Making chords with the Web Audio API

I just found out about the super cool Web Audio API and tried it out. (demo)

It allows you to set up Audio objects called nodes, set their attributes, and spin them up and down to create sounds in the browser.

The API documentation on MDN is quite nice to get started. Basically, if you haven’t made the browser make little buzzing sounds before, this is your chance to do it.

The easiest way to understand how it works was to begin just making some noise. So let’s do that.

First, you need to set up a new “AudioContext” which all your other audio things will need to exist in to do anything. AudioContext contains your nodes and connects them to the browser’s audio playback mechanism.

const audioCtx = new (window.AudioContext || window.webkitAudioContext)();

A simple kind of audio “node” that we can create is called an OscillatorNode.

The OscillatorNode creates a basic sound wave.

You may remember learning at one point how capital “S” Sound is actually a wave and this API makes this really real.

It turns out that a sound wave can “wave” in different patterns like a “sine”, “square”, “sawtooth”, and “triangle” and each of those wave patterns sounds a bit differently to your ear when you hear it.

Let’s make our OscillatorNode do a “sawtooth” wave and give it a frequency value for how fast to wave back and forth.

We use the AudioContext.createOscillator method to make our node. And then set attributes on it, connect it to our audioContext, and start our wave. Commence oscillation!

You may want to have your volume kind of low for this at first because I found the oscillator wave sounds kind of loud versus other media you’d usually watch at the same volume…

let osc = audioCtx.createOscillator();
osc.frequency.value = 523;
osc.type = "sawtooth";
osc.connect(audioCtx.destination);
osc.start()
osc.stop()

Okay, cool so we made a sound! In the browser. That’s completely sweet.

We did have to set a value for the frequency of our OscillatorNode.

It’s actually in “Hertz,” which I remember learning about at one point but never really had used for anything until now. Hertz is a frequency unit which refers to cycles per second.

So when we set frequency to 523 our sound wave is oscillating 523 cycles each second in a “sawtooth” form. What is sound made of? It’s a wave… what’s the wave made out of though?

The wave of sound is actually a pressure wave of expanding and contracting air. Okay, that’s pretty awesome.

I also found out that you can pretty easily look up the Hertz frequency of different musical notes, so let’s do that. For example, 523.25 Hz is a C note. And middle C on a piano is 261.63 Hz, or 261.63 cycles per second.

If the vibration of the sound wave has less frequent cycles, it sounds deeper pitched, and if it has more frequent cycles it will sound higher pitched.

You can set your osc.frequency.value to some other value and it will change the sound. You can also stop your node and start a new OscillatorNode with a new frequency.

So this is fun! We can make a doSound function that can accept values like hertz, type, or a timeout value… and will let us do a bunch of sounds at different times all at once and faster.

const doSound = (hzValue=523) => {
	let osc = audioCtx.createOscillator();
	osc.frequency.value = hzValue;
	osc.type = "sawtooth";
	osc.connect(audioCtx.destination);
	osc.start()
	setTimeout(() => { osc.stop() }, 500)
}

Cool, so we can make sounds with our function, and it will use the glorious and buzzy “sawtooth” wave.

Let’s map out some musical note names into Hertz so we can call them easier.

NOTE_HZ_MAP = {
	A: 220,
	B: 246.94,
	C: 261.63,
	D: 293.66,
	E: 329.63,
	F: 349.23,
	G: 392
}

const doChord = (str) => {
	let notes = str.split("");
	notes.forEach((n) => {
		let hz = NOTE_HZ_MAP[n];
		if (hz != undefined) {
			doSound(hertzValue=hz);
		}
	})
};

So this is cool because now we can also make a few doSound() calls at once and make a chord of musical notes.

If we wanted to make a major C chord we’d just do doChord(“CEG”).

We could hook up chords to buttons on a page or to keypress events… or we could even set up some sort of chordProgression function to call our chords in a row…

const chordProgression = (str, count=0) => {
	let progression = str.split("");
	let i = count;
	setTimeout(() => {
			let n = progression[count]
			console.log(n);
			if (n === "1") {
				doChord(chord1);
			} else if (n === "2") {
				 doChord(chord2);
			} else if (n === "3") {
				 doChord(chord3);
			} else if ( n === "4") {
				doChord(chord4);
			}

			if (i < progression.length) {
			  i += 1
			  chordProgression(str, i);
		  } else {
		  	return console.log("done");
		  }
		}, 510);
}

And then we could give it a string that we’ve set up like…

var myCoolChords = "11111111222222221111111122222222333311113333111133331111222222221111111122222222333311113333111122222222111133331111333322222222"

chordProgression(myCoolChords);

If we also set up some keypress listeners and map those to hertz values we can press notes on the keypad while our chord progression runs…

const listenForKeyPress = () => {
	document.addEventListener("keypress", (e) => {
		let pressed = e.keyCode;
		let hz = KEYCODE_HZ_MAP[pressed];
		console.log(pressed + ", " + hz);
    if (hz != undefined) {
    	doSound(hz);
    }
  })
}

KEYCODE_HZ_MAP = {
	97: 220,
	115: 246.94,
	100: 261.63,
	102: 293.66,
	103: 329.63,
	104: 349.23,
	106: 392.00,
	107: 369.99,
	108: 440,
	59: 523.251
}

listenForKeyPress();

Now you can click a button to start the chord progression, or write your own chord progression in the fields, and then use the keypad to rock a sweet little wave solo!

An idea for more functionality: we could add a dial to set the musical key which adjusts all the notes or a way adjust the volume on some of the chords… this demo needs more functionality stat. And lots more bass!

Anyways, this seems to be about 0.1% of what you can do with this cool API.

There are lots of other types of nodes I didn’t check into and the ability to chain them together. And along with set waveforms “sine” or “sawtooth” there is the ability to do a “custom” wave that you explicitly set and control more properties on it.

So that’s it for now. It turns Web Audio is very fun.

And here is some more cool Web Audio stuff to look at: