Building a Modular Synth With JavaScript and Web Audio API

Rick Moore
Geek Culture
Published in
7 min readApr 8, 2021

Advances in studio recording technology has undeniably changed the landscape of the music industry over the last decade, allowing producers to create and release music from smaller workspaces, with a workflow approaching the speed of thought.

One of my favorite side effects of this new culture is how these production technologies are making their way into the world of web development. One prime example is now a standard feature built right into your browser: The Web Audio API brings music production tools right to your fingertips, allowing you to build synthesizer and effect emulators with nothing more than vanilla JavaScript. Even more exciting are the ways that the Web Audio API can be used with other packages and tools to create new integrated experiences on the web.

If you have spent any time in the music production world and/or the coding and development world, this article is for you. I think you’ll be as excited as I was when I discovered all this amazing tool has to offer.

Modular synthesizers are musical instruments that typically are not played with a keyboard or some other controller, but use software or hardware sound generators and modulators to create music through parameters and settings. Producers will connect several modules together with patch cables, to create unique signal flows and sounds that have never been heard before.

Photo by Adi Goldstein on Unsplash

The tools provided with Web Audio API mimic the technology used in these modules, so once we lay down the basic code framework, we can start experimenting in the same way. I’ll show you how to get started and we’ll build a simple modular synth here together.

1. Let’s Start With The Basics

I sat down with a JavaScript project idea in mind, and had a major hurdle to jump right at the top: Building a sampler that I could load my own audio samples into, making them playable with the computer keyboard, with some basic filters and delays to fiddle with.

The Web Audio API came up in my searches, and what first looked like an intimidating amount of code, I realized was actually structured with the signal flow of real world audio devices.

We start by creating an audio context in our JS file. If you are familiar with the idea of a canvas in JS, the audio context is like the canvas we are going to build our soundscape on. There is typically only 1 audio context per page. We can start with this bit of code which allows this to work on all browsers:

var AudioContext = window.AudioContext ||
window.webkitAudioContext;
const context = new AudioContext;

Just like a real audio system, we can use our AudioContext object to create a sound source. This can either be:

a ) live input like your computers microphone

b) an audio file or sample

c) an oscillator, or software sound generator

And just like any other audio signal chain, this source audio goes through some effects or modulation, and eventually gets connected to an audio output, or destination. This is like the audio eventually reaching some speakers:

Let’s build out our basic framework for our modular synth with a master volume control:

const masterVolume = context.createGain();
masterVolume.connect(context.destination);

and an oscillator as the sound source:

const oscillator = context.createOscillator();
oscillator.frequency.setValueAtTime(220, 0);
oscillator.connect(masterVolume);

We use the connect() keyword to make connections between modules, just like connecting the output of one audio device to the input of another. In this example we are setting the frequency property of the oscillator to 220Hz, at the 0 time index(or as soon as the play() method is invoked). Take a look at the docs for the Oscillator Node to see all it’s capable of. In the following example, I set up a simple oscillator with an on and off button, and some switches to select a base waveform to generate:

Alright! We have some buttons to select our waveform (a basic principle in sound synthesis), that waveform is triggered by a button, and we are outputting sound through the browser! We are off to a good start!

2. Adding an Envelope

Not the kind you send a letter in. “What’s a letter?” you ask? Don’t worry about that. In music synth, an envelope is an amplitude effect typically made up of 4 values: (A)ttack time, (D)ecay time, (S)ustain level, and (R)elease time, or ADSR. These values usually apply to the change in amplitude, or loudness of a sound over time.

The attack is how long it takes the sound to ramp up to full volume, the decay is the time it takes for the sound to reach the sustain level, which holds for a period of time, until the release, which is how long it takes the sound to fade away to 0. With a midi keyboard, we typically control the decay and sustain times by holding a key down, but most modular synths have a way to adjust the time of these values manually. Web Audio API doesn’t have an envelope built in, but we are able to simulate the effect using gain nodes. Let’s build a single oscillator with a simple envelope, with an adjustable attack and release. To do this, we are going to need a couple tools from our Web Audio API toolbox:

context.currentTime allows us to tap into the precise clock that our context holds. This gives us much more precision on our events and allows us to trigger several events over a concrete timeline.

linearRampToValueAtTime() works similarly to setValueAtTime(), letting us change a value over time.

Let’s create a gain node along with our oscillator so we can have independent control over each note we play:

In this example, I set the length of our note to 1 second using the stop() method, and set sliders to adjust the attack and release from 0 to 0.5 seconds. This is a simple example of how to implement an envelope filter on any sound source we have playing in our audio context. We are close to a framework to create a basic modular instrument.

3. Adding an LFO

An LFO, or Low Frequency Oscillator is another backbone of music synthesis. Oscillators are simply waveform generators, and when an object (like the cone of a speaker) vibrates between 20 times and 20,000 times per second (20Hz to 20kHz), our ears pick up that vibration as sound. A wave at 14kHz for example would be piercingly high (or outside the range of hearing for some folks), while a wave at 80Hz would be heard as a low bass sound. Considering this, an oscillator is simply creating many pieces of data every second, that can be reproduced by a speaker OR used to continually update the value of another parameter. If we set an oscillator to a frequency of 10Hz, for example, we would get an output moving between 2 values 10 times every second. This could be used to adjust the frequency of another oscillator, or the gain value of a gain node… or anything else, if we feel like experimenting. But for now, let’s set up something simple in our code. We’ll use an LFO (which is just an oscillator) to modulate the frequency of another oscillator, which is generating a tone. This is an effect musicians would call vibrato, and the slight variations in pitch add richness to the tone.

As you can see, we’ve simply created an oscillator, called lfo and set its output to a gain node, and the gain node to the frequency input of the note oscillator. By adjusting the amplitude of the LFO with the gain node, and the frequency of the oscillator with its own frequency property, we now have control over the vibrato effect of the sound. The result is something resembling a violinist moving their left wrist when they play, adding richness and expression to the tone.

4. Bringing it all together

With a base understanding of the effects we want to build into our modular synth, let’s combine all the code we’ve written so far! The last thing I’d like to add is a step sequencer, with 8 steps that will play 8 selected notes in order and loop them. We’ll be able to adjust the attack, release, and vibrato of the notes, and the order in which the notes play. I’ll also add in a simply delay which is built in to the Web Audio API. This will add a “repeating” effect to our sounds.

Without diving too deep into the weeds of music theory, I’ll start by declaring an array of frequencies that will coincide with one octave split into its 12 semitones. We can then use html selects to sequence the notes, and a couple of buttons to start and stop our sequence:

This is only scraping the tip of the iceberg of possibilities of what the Web Audio API is capable of. Using the mapped HTML tools, we are already able to create some interesting sound textures just by experimenting with the settings. This type of abstraction is the heart of modular synthesis, and having these tools available in JavaScript make the possibilities endless.

Please check out these additional resources to explore more!

--

--

Rick Moore
Geek Culture

Software Engineer at Thrive TRM. Specializing in Ruby on Rails, React/Redux and JavaScript.