Objective

The goal of my project was to build a basic version of a wavetable synthesizer that can be plugged into a DAW and work cross-platform. It can deal with incoming midi messages and produces different wave forms. Moreover, inspired by ReaMote and Reaper, I decided to add in support for remote control as well provided with the IP address and port number of the host. A user can use any OpenSoundControl applications to change the behavior of the synthesizer, all operations can be performed from the host as well.

Technical Stack

JUCE v6.0.3 (Develop Branch)

JUCE is a framework built in C++ for cross-platform audio development. It supports building on PC, Mac, Linux, Android and IOS for plug-in formats including AU, VST and AAX. JUCE constitutes the back bone of this project and I used it as a bare minimum to start. For this project specifically, I utilized the synthesizer module to hold the DSP algorithms and the OSCReceiver class to receive and parse the OSC messages

Maximilian

Maximilian is a library for audio synthesis and signal processing built in C++. Unlike JUCE, it is a less opinionated framework, which gives the user more control over the application and its DSP modules are simpler to implement, especially considering I have limited experience with DSP. I decided that it is a good idea to import this library as a third-party tool to ease the learning curve on DSP algorithms.

FRUT

FRUT is a third-party exporter that converts an X-code build of a JUCE project into a Cmake project. I used it so that I do not have to deal with any surprises from X-code.

Binaries

The binaries for this project can be found here (.app file built on MacOS Big Sur) https://github.com/VincentCloud/Synthesizer/releases/tag/v1.0.

Project Architecture and Implementations

The overall structure of this project is similar to a typical JUCE project, where different parts of the application are divided into hierarchical components.

A rough architecture diagram on the structure of the synthesizer

A rough architecture diagram on the structure of the synthesizer

As shown in the diagram, the two main components of this application are SynthesizerAudioProcessor and SynthesizerAudioProcessorEditor, which are responsible for the DSP algorithms and UI, respectively. SynthSound.h and SynthVoice are utilized to support the I/O feature.

DSP Components

On the DSP aspect, SynthVoice class extends from JUCE's built-in SynthesiserVoice class, where the parent takes care of the sampling, input and output of the sound. In order to make this application work as a standalone app as well as a plugin usable in DAW, I decided to convert the midi message into wave frequency to produce an output. This is simpler than relying on a midi output device and it simplifies the "pitch bend" feature significantly. SynthesiserVoice follows an event-driven protocol, which means it keeps a message loop on a separate thread to detect different events from the input devices such as "note on", "note off", "pitch bend", etc. When a event is detected, a callback function will be triggered to perform certain operations. For example, in pitchWheelMoved, it detects the "pitch bend" midi message and changes the frequency of the sample accordingly. renderNextBlock gets called by the main DSP component to output the sample block. Since the DSP component works on an audio thread where the sampling happens at a very high rate, it is critical to leave out any standard input / output messages as they could slow down the system and cause delay.

UI Components

On the UI side, I followed the conventional way of making a GUI application in JUCE having the SynthesizerAudioProcessorEditor as the main UI component. Its child components are Oscillator and Envelope. The Oscillator component is mainly responsible for showing up the options of wave forms in a combo box, which enables the user to select from sine wave, saw wave and square wave. This component takes up roughly 1/3 of the width of the main window. On the other hand, the Envelope component helps the user modify the behavior of the wave. In this project specifically, I built an adsr synthesizer, which has "attack", "decay", "sustain" and "release" as the parameters. And the user is able to change the parameters through the sliders.