JUCE Basics Part-1

Introduction

For this experiment you will need to setup JUCE and your favorite development enviroment (Getting started with the Projucer). I'm going to use Visual Studio. We are going to create a simple plugin, starting with a blank project. Our goal is to better understand the "Basic Plugin" template, from Projucer.

In this first part we are going to begin with a JUCE GUI application, and subsequently change the Projucer project to a Plugin project.

Creating the project

Open the Projucer > New Project > Application > Blank. Then give it the project name, I will name it "BasicPlugin". Expand the modules tab, we can see that a few modules are checked. Check if the modules path are correct. Select your exporter and create the project.

Creating the project

Take a look on the project's folder that was created, we have our JUCER file that receives the project name you chose , and three folders (Source, JuceLibraryCode and Builds).

Basic Plugin Folder

And Projucer looks like this

Projucer Basic Plugin file

The main function in JUCE

Let's create a new file:

  • Make sure to expand the file explorer
  • Right click on Source folder (called Group in Projucer)
  • Then, select "Add New CPP File"
  • Give it a name, in my case I will name it "Main"
  • Go to File > Save Project or use shortcut "ctrl + p"

A blank .cpp file will be created on Source folder. Check the project's folder. Next, we'll open our chosen exporter, in this case within Projucer. I will open Visual Studio.

Open Visual Studio inside projucer

The "Blank" template, creates a blank JUCE GUI application. Replace the "#include"

#include "Main.h" // delete this
#include <JuceHeader.h> // Replace for this

To start our JUCE GUI application (not a plugin yet) we need to use a macro "START_JUCE_APPLICATION()"

#include < JuceHeader.h >

START_JUCE_APPLICATION() // this will active spellcheck, because needs an argument.

You can jump to definition (ctrl + left click on the macro) where the macro is defined

We can see another definition that looks like where the main function resides

Yep, looks like we are returning something like a main function. A method of class called JUCEApplicationBase Let's jump one more time on that class ...

Now we can see how the class is defined, there are many comments to read at this point, and the main() function with a comment above saying that it is for internal use only. We're going to stop looking at the JUCE details for now.

The JUCE Application Base Class

This first big comment above the class definition, it is exactly the same of thedetailed description of this class in the documentation. "Any application that wants to run an event loop must declare a subclass of JUCEApplicationBase, and implement its various pure virtual methods. It then needs to use the START_JUCE_APPLICATION macro somewhere in a CPP file to declare an instance of this class and generate suitable platform-specific boilerplate code to launch the app."

Let's create our class named "App" that inherits from JUCEApplicationBase class, and pass it to the macro argument

#include <JuceHeader.h>

class App : juce::JUCEApplicationBase
{   
public:
     App() {}
    
private:
};

START_JUCE_APPLICATION(App) // this will active spellcheck, because we need to 
                            // implement all virtual methods of the base class 

As it is an abstract class we must implement all virtual methods.

#include <JuceHeader.h>

class App : public juce::JUCEApplicationBase
{   
public:
     App() {}

     const juce::String getApplicationName() override { return ProjectInfo::projectName; }
     const juce::String getApplicationVersion() override { return ProjectInfo::versionString; }
     bool moreThanOneInstanceAllowed() override { return true; }

     void initialise(const juce::String& commandLine) override {  }
     void shutdown() override {  }
     void systemRequestedQuit() override {  }
     void anotherInstanceStarted(const juce::String& commandLine) override {  }
     void suspended() override {  }
     void resumed() override {  }
     void unhandledException(const std::exception*, 
                             const juce::String& commandLine,
                             int lineNumber) override {  }

private:
};

START_JUCE_APPLICATION(App)

Right now, we can run our application. It is only a background process, a infinite loop that drives the operation of the application, open your task manager and search for the BasicPlugin Process, or after compilation and build, look for the binary on the projects folder, but make sure to end the task in the task manager.

Classic Hello World!

The initialise method is called when the application starts. After this method returns, the normal event-dispatch loop will be run. (see JUCE documentation) Let's print "Hello World!" on the console. We are going to use the writeToLog method of the juce::Logger helper class for this job.

void initialise(const juce::String& commandLine) override 
{
   juce::Logger::writeToLog("Hello World!");
}

Try to build and run. The output should be ...

"BasicPlugin.exe" (Win32): Carregado "C:\Windows\System32\msctf.dll". 
HelloWorld!

Ok, that's great, but if we need perform tasks that need to be done or cotrolled in the main event loop? We can use the timerCallback method for this. Let's print 'Hello World' every 1 second indefinitely. Our class needs to inherit from the juce::Timer class, and we call the startTimer() method on initialise() and stopTimer() on systemRequestedQuit(), and then we implement the timerCallback() method.

#include <JuceHeader.h>
    
class App : public juce::JUCEApplicationBase, juce::Timer
{   
public:
     App() {}
    // ...
     void initialise(const juce::String& commandLine) override 
     {
        startTimer(1000);
     }
     
     void systemRequestedQuit() override 
     { 
        stopTimer();
        quit();
     }

     void timerCallback() override 
     { 
        juce::Logger::writeToLog("Hello World!");
     }
    // ...

The output ...

"BasicPlugin.exe" (Win32): Carregado "C:WindowsSystem32msctf.dll". 
HelloWorld!
HelloWorld!
HelloWorld!
HelloWorld!
HelloWorld!
HelloWorld!

GUI

To show something on the screen we can use something that inherits from JUCE's base class for all user-interface objects, the juce::Component. I will create a new class above our App class that inherits from DocumentWindow and declare an MainWindow attribute.

class MainWindow : public juce::DocumentWindow
{
public:
    MainWindow(juce::String name)
        : DocumentWindow(name,
                        juce::Desktop::getInstance().getDefaultLookAndFeel()
                        .findColour(juce::ResizableWindow::backgroundColourId),
                        DocumentWindow::allButtons)
    {
        setUsingNativeTitleBar(true);
        setContentOwned(new Component(), false);

        

        setResizable(true, true);
        centreWithSize(getWidth(), getHeight());


        setVisible(true);
    }

    void closeButtonPressed() override
    {
        juce::JUCEApplicationBase::getInstance()->systemRequestedQuit();
    }

    private:
};

class App : juce::JUCEApplicationBase, juce::Timer
{   
public:
     App() {}
    // ...

    // ..
private:
    std::unique_ptr<MainWindow> myWindow;
};

In the initialise method, we have some set things to do.

// ...
void initialise(const juce::String& commandLine) override 
{
    myWindow.reset(new MainWindow(getApplicationName()));
    myWindow->setBounds(0, 0, 600, 400);
   
    startTimer(1000);
}

void shutdown() override 
{
    myWindow = nullptr;
}
// ...

After build, should look like this

App Window

Let's show some text. We are going to speed up our workflow a little, go to Projucer. Expand File Explorer > Right click Source group > Add New Component class (split between a CPP & header) > Give it a name (I will name it MyComponent) Don't forget to "Save the project" (ctrl + p). In our Main.cpp ...

#include <JuceHeader.h>
#include "MyComponent.h"

Just change the setContentOwned argument from our MainWindow class

setContentOwned(new MyComponent(), false);

Open MyComponent.cpp and go to the paint method and change the string argument to "Hello World!".

g.drawText("Hello World!", getLocalBounds(),
juce::Justification::centred, true);   // draw some placeholder text

After build, should look like this

App Window Hello World

And we are done. As an exercise, try to write using the juce::String.