Robert Moog famously created one of the very first commercial modular synthesizers. His inventions consisted of banks of wires and knobs, allowing musicians to create sounds never heard before. These instruments weren't cheap either, costing thousands of dollars for even the most basic model.
Now, thanks to the Web Audio API, we can create our own similar-sounding synth that we can configure to our heart's content for the grand total of $0. Not only that, we can distribute our synth instantly to anyone in the world thanks to the web.
If you haven't read the previous tutorials in this Web Audio series, I'd suggest going to back and reading them before undertaking this one, as they cover the basics of using the Web Audio API and creating a sound from scratch.
Getting Started
Let's start by creating a basic HTML page.
<!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>Add sound to your web app</title> <link rel="stylesheet" href="styles/main.css"> </head> <body> <div class="container"> <h1>Synthesizer!</h1> </div> </body> </html>
And some basic styling in our styles/main.css
file.
body { font-family: sans-serif; } .container { margin: auto; width: 800px; }
Keyboard
Perhaps the most important thing your synth needs is a keyboard. Luckily, I've created a little snippet of JavaScript that will add a virtual keyboard to your page. Download a copy of Qwerty Hancock and reference it at the bottom of your page like so.
<script src="scripts/qwerty-hancock.min.js"></script>
Then add an empty div to your page with an id of "keyboard".
<div id="keyboard"></div>
This is the place on the page that the keyboard will be inserted into.
We'll also want to set up a dedicated JavaScript file for our synth, so let's create that too and reference it after where we've included Qwerty Hancock.
<script src="scripts/qwerty-hancock.min.js"></script> <script src="scripts/synth.js"></script>
Within synth.js we'll initialise our keyboard by doing the following.
var keyboard = new QwertyHancock({ id: 'keyboard', width: 600, height: 150, octaves: 2 });
This tells our page to insert an instance of our keyboard into the element with the id of "keyboard", resize it to 600 x 150 px, and make the number of keys on the keyboard cover two octaves. Save and refresh your browser to see a lovely onscreen keyboard. Use keys, touch, or your mouse to see notes light up as you press them.
Qwerty Hancock provides us with two event listeners, keyUp
and keyDown
. These allow us to hook into the keyboard and write code that fires when the keyboard is pressed. It also tells us which note was pressed, and its corresponding frequency in hertz.
keyboard.keyDown = function (note, frequency) { console.log('Note', note, 'has been pressed'); console.log('Its frequency is', frequency); }; keyboard.keyUp = function (note, frequency) { console.log('Note', note, 'has been released'); console.log('Its frequency was', frequency); };
Oscillate Wildly
Let's start an oscillator when a key is pressed. We'll stop it after one second so it doesn't go on forever.
var context = new AudioContext(); keyboard.keyDown = function (note, frequency) { var osc = context.createOscillator(); osc.connect(context.destination); osc.start(context.currentTime); osc.stop(context.currentTime + 1); };
Why are we creating the oscillator inside the keyDown
function? Isn't that inefficient? Oscillators are designed to be lightweight and to be thrown away after use. You can actually only use them once. Think of them as some sort of bizarre audio firework.
Now when we press a key, we hear a sound. It's a bit loud, so let's create a gainNode
to act as master volume control.
var context = new AudioContext(), masterVolume = context.createGain(); masterVolume.gain.value = 0.3; masterVolume.connect(context.destination); keyboard.keyDown = function (note, frequency) { var osc = context.createOscillator(); osc.connect(masterVolume); masterVolume.connect(context.destination); osc.start(context.currentTime); osc.stop(context.currentTime + 1); };
A keyboard that plays one single note over and over isn't very fun, so let's plug in the frequency to the oscillator before we start it playing.
osc.frequency.value = frequency;
Lovely. We now need to stop the oscillator playing after we lift a key rather than after a second. Because we're creating the oscillator inside the keyDown
function, we need to keep track of which oscillator is playing which frequency in order to stop it when the key is released. A simple way of doing this is to create an empty object and add the frequencies as keys, along with which oscillator is playing that frequency as its value.
var oscillators = {}; keyboard.keyDown = function (note, frequency) { // Previous code here oscillators[frequency] = osc; osc.start(context.currentTime); };
This means we can easily use the frequency we get from our noteUp
function to stop that specific oscillator.
keyboard.keyUp = function (note, frequency) { oscillators[frequency].stop(context.currentTime); };
We now have a fully working (very basic) synthesizer in the browser! Ok, it doesn't sound great at the moment, but let's see if we can change that.
The first thing to do is change the type of wave the oscillator outputs. There are four basic types to choose from: sine, square, triangle and sawtooth. Each different shape of wave sounds different. Play about with them and choose your favourite. For this example, I'll choose "sawtooth".
osc.type = 'sawtooth';
There, that sounds better.
It's very rare you'll find a synthesizer that uses a single oscillator. Most synths beef up their sound by combining different oscillators of different types. Let's see how it sounds if we add another. Remember, we need to double up all our connections, and we'll need to add oscillators of the same frequency to an array. This means we can iterate over them in order to stop all of the oscillators that are playing the same note.
keyboard.keyDown = function (note, frequency) { var osc = context.createOscillator(), osc2 = context.createOscillator(); osc.frequency.value = frequency; osc.type = 'sawtooth'; osc2.frequency.value = frequency; osc2.type = 'triangle'; osc.connect(masterVolume); osc2.connect(masterVolume); masterVolume.connect(context.destination); oscillators[frequency] = [osc, osc2]; osc.start(context.currentTime); osc2.start(context.currentTime); }; keyboard.keyUp = function (note, frequency) { oscillators[frequency].forEach(function (oscillator) { oscillator.stop(context.currentTime); }); };
To finish things off, let's use a trick we learned in the previous tutorial. We can add a bit of chorus to add more shimmer to our sound, by detuning the oscillators slightly.
osc.detune.value = -10; osc2.detune.value = 10;
Beautiful, a synth Kraftwerk would be proud of! Play along with the finished article, or fork the repo on GitHub to tweak the synth to your heart's content.
Comments