⚠ WARNING ⚠: this website is no longer maintained (and hasn't been for a while).

To view my active website, please visit https://abstract.properties


Part One: Oscillation and Gain

This article will show how to create tones of varying volume with the Web Audio API.

History

The Web Audio API is a comprehensive API that allows for both simple and complex operations on sound primitives. Introduced in 2011, the Web Audio API is the surviving of the two originally-proposed audio-on-the-web APIs, with the alternative (the Audio Data API) proposed by Firefox. The Audio Data API was a more low-level API, that had hardly any built-in tools and abstractions to effectively manipulate audio.

The Web Audio API follows in the trend of rich web APIs that, while difficult for the browser vendors to implement, provide powerful abstractions to create useful and performant applications in a small amount of code.

Currently, the API is only supported in Firefox and WebKit-based browsers (Chrome and Safari) 2.


Diving in: making a beep

Let's do the simplest thing that could possibly work: create a short tone.

First, we must build an audio context from which everything happens:


window.audioContext = new (window.AudioContext || window.webkitAudioContext)();
            

Then, we can actually get things working:


var oscillator = window.audioContext.createOscillator();
oscillator.frequency.value = 440; // The A above middle C
oscillator.connect(window.audioContext.destination);
oscillator.noteOn(window.audioContext.currentTime);
oscillator.noteOff(window.audioContext.currentTime + 1.0);
            

So what just happened?

Not that much, actually. We created an OscillatorNode instance that vibrates at 440 hz, and connected it to the audioContext's "destination", which goes to your speakers. Then, we told the oscillator to turn on between now and 1.0 seconds from now.


Let's play a song.

The oscillator we just created is pretty simple, so let's see if we can play the NBC chimes:


var n = window.audioContext.createOscillator();
n.frequency.value = 195.998; // G3
n.connect(window.audioContext.destination);

var b = window.audioContext.createOscillator();
b.frequency.value = 329.628; // E4
b.connect(window.audioContext.destination);

var c = window.audioContext.createOscillator();
c.frequency.value = 261.626; // C4
c.connect(window.audioContext.destination);

n.noteOn(window.audioContext.currentTime);
n.noteOff(window.audioContext.currentTime + 0.25);

b.noteOn(window.audioContext.currentTime + 0.5);
b.noteOff(window.audioContext.currentTime + 0.75);

c.noteOn(window.audioContext.currentTime + 1.0);
c.noteOff(window.audioContext.currentTime + 1.25);
            

Hmm, there's lots of duplication here. Let's factor out some common code:


window.frequencies = {
    'G3': 195.998,
    'E4': 329.628,
    'C4': 261.626,
};
window.playNote = function (note, time, duration) {
    var ctx = window.audioContext;
    var osc = ctx.createOscillator();
    osc.frequency.value = frequencies[note];
    osc.connect(ctx.destination);
    osc.noteOn(ctx.currentTime + time);
    osc.noteOff(ctx.currentTime + time + duration);
    return osc;
}
            

And then make it more presentable:


playNote('G3', 0.0, 0.25);
playNote('E4', 0.5, 0.25);
playNote('C4', 1.0, 0.25);
            

Not the most pleasant sounding fanfare, but it'll do for now.


Controlling Volume

Oscillators aren't the only actor we have in this API, we actually have many different types of nodes that give us a huge amount of flexibility.


var oscillator = window.audioContext.createOscillator();
oscillator.frequency.value = 440;
oscillator.noteOn(window.audioContext.currentTime + 0.00);
oscillator.noteOff(window.audioContext.currentTime + 2.25);

var gain = window.audioContext.createGain();
gain.gain.setValueAtTime(0.05, window.audioContext.currentTime + 0.00);
gain.gain.setValueAtTime(0.00, window.audioContext.currentTime + 0.25);
gain.gain.setValueAtTime(0.10, window.audioContext.currentTime + 0.50);
gain.gain.setValueAtTime(0.00, window.audioContext.currentTime + 0.75);
gain.gain.setValueAtTime(0.20, window.audioContext.currentTime + 1.00);
gain.gain.setValueAtTime(0.00, window.audioContext.currentTime + 1.25);
gain.gain.setValueAtTime(0.40, window.audioContext.currentTime + 1.50);
gain.gain.setValueAtTime(0.00, window.audioContext.currentTime + 1.75);
gain.gain.setValueAtTime(0.80, window.audioContext.currentTime + 2.00);

gain.connect(audioContext.destination);
oscillator.connect(gain);
            

The GainNode type is a node which takes input and produces an output with the volume turned up or down.

There are a lot more nodes available, but this article will only focus on GainNode and OscillatorNode.


Building Something Interactive

Even though only have two tools in our arsenal, we have enough to be able to build something interactive.

A theremin is an instrument that allows you to control the pitch and volume by waiving your hands in the air. It usually is used in B-movie sci-fi flicks for spooky sound effects, but it also can be used as a serious musical instrument.

Let's put together a basic theremin with a single oscillator and a gain:


var oscillator = window.audioContext.createOscillator();
var gain = window.audioContext.createGain();
gain.gain.value = 0;

oscillator.connect(gain);
gain.connect(window.audioContext.destination);
oscillator.noteOn(0);

var on = false;
var mouse = { x: 0, y: 0 };
var theremin = document.getElementById('theremin');

function update() {
    oscillator.frequency.value = Math.pow(mouse.x, 2) * (3520 - 44) + 44;
    gain.gain.value = on ? Math.pow(mouse.y, 2) : 0;
}

theremin.addEventListener('mousemove', function (event) {
    mouse.x = (event.pageX - theremin.offsetLeft) / 500;
    mouse.y = 1 - (event.pageY - theremin.offsetTop) / 500;
    update();
});

theremin.addEventListener('click', function (event) {
    on = !on;
    update();
});
            

And to demonstrate the silliness:

Click here to turn on/off the theremin (and make your pets go crazy).

In Conclusion

The Web Audio API excels at easily connecting and controlling various components to its default output (your speakers), and provides the basic building blocks for making things beep.

In the next segment, we'll cover a bit of music theory: Equal Temperament, Notes, and Notation.