GrabDuck

Generate Sounds Programmatically With Javascript

:

During a recent hackathon, I decided to build a multiplayer 8 bits sequencer using sounds generated programatically with the Web Audio API. I didn’t want to only use the HTML 5 audio tag because I found it too limiting… but the first thing I discovered is that getting the right kind of sound is not straightforward at all, especially if you only have a very basic musical background like myself. So how exactly do you create a clear, ringing and nice sounding note?

In this article I’ll give some pointers with usable code. I’ve also added examples that you can actually run if your browser supports it.

Produce a Simple Beep

First let’s create a very basic beep using a sinusoid. We’ll initiate an audio context, which is the central object for generating sound. Then we’ll create an oscillator producing the sine wave. Finally we connect the oscillator to the context and start.

var context = new AudioContext()
var o = context.createOscillator()
o.type = "sine"
o.connect(context.destination)
o.start()

Play Stop

You’ll notice that the sound produced here is not great. It seems like you let a phone off the hook and when you stop it, you hear a “click” and it’s not pleasant at all. This is because the human hear reacts this way as explained in this great article. Basically when you stop the sound anywhere else than the zero crossing point, you’ll hear this clicking sound.

Getting Rid Of The Clicking Sound

The best solution to get rid of this click is to ramp the sine wave down with an exponentional function, using AudioParam.exponentialRampToValueAtTime() as documented here.

This time we need to add a gain node to our oscillator. A gain node allows us to change the volume of a signal as explained in this schema from the documentation:

The code to start the sound now looks like this:

var context = new AudioContext()
var o = context.createOscillator()
var  g = context.createGain()
o.connect(g)
g.connect(context.destination)
o.start(0)

Play

In order to stop the sound we change the gain value, effectively reducing the volume. Note that we don’t ramp down to 0 since there is a limitation in this function where the value has to be positive.

g.gain.exponentialRampToValueAtTime(
  0.00001, context.currentTime + 0.04
)

Stop

As you can hear, the clicking sound is gone! But that’s not the only interesting thing that you can do with this exponential ramp down.

Set A Ringing Effect

In the example above, we decided to stop the sound really quickly, in 0.04 seconds. But what happens when we change this X value?

g.gain.exponentialRampToValueAtTime(0.00001, context.currentTime + X)

Play Stop (X=0.1) Stop (X=1) Stop (X=5)

Hitting Notes

Giving more time to the sound to fade out gives it a totally different feel. It gets more visible when we start and stop the signal right away:

Start and stop quickly Start and stop slowly

The first one sounds like a ticking noise when the other sounds like an actual note played on an instrument.

Various Oscilators

So far we’ve been using a sine wave for our main signal, but we have other options:

It’s enven more interesting when we start playing around with the type of oscilators by setting o.type = type.

Sine Square Triangle Sawtooth

Playing Actual Notes

With the previous code, it becomes fairly simple to have a nice sounding note, but what exactly were we playing? That’s when you have to take frequency into account. For instance, the one people know is that A4 is 440Hz, but there are others.

Note Frequencies in Hz

With this table, you can easily create a mapping in your code to play any given note using its . For the hackathon I used a simple hash mapping that is available in this gist.

C C# D Eb E F F# G G# A Bb B
0 16.35 17.32 18.35 19.45 20.60 21.83 23.12 24.50 25.96 27.50 29.14 30.87
1 32.70 34.65 36.71 38.89 41.20 43.65 46.25 49.00 51.91 55.00 58.27 61.74
2 65.41 69.30 73.42 77.78 82.41 87.31 92.50 98.00 103.8 110.0 116.5 123.5
3 130.8 138.6 146.8 155.6 164.8 174.6 185.0 196.0 207.7 220.0 233.1 246.9
4 261.6 277.2 293.7 311.1 329.6 349.2 370.0 392.0 415.3 440.0 466.2 493.9
5 523.3 554.4 587.3 622.3 659.3 698.5 740.0 784.0 830.6 880.0 932.3 987.8
6 1047 1109 1175 1245 1319 1397 1480 1568 1661 1760 1865 1976
7 2093 2217 2349 2489 2637 2794 2960 3136 3322 3520 3729 3951
8 4186 4435 4699 4978 5274 5588 5920 6272 6645 7040 7459 7902

To implement this, we just need to add a frequency to our oscilator:

var frequency = 440.0
o.frequency.value = frequency

If we change with the value of frequency, we can play any note. For instance:

261.6Hz (C4) 440Hz (A4) 830.6Hz (G#5)

Mix this with the ramp down timings and different signals, and you start to be able to create more interesting sounds.

174.6Hz (F3) - Square 1109Hz (C#6) - Sawtooth 87.31 Hz (F2) - Triangle