Making Music with The WebAudio API Part 3

At the end of Part 2 of this series, we had a page that lets a user play and stop a tone and to specify a musical note pitch for tones relative to Middle A.

This always produces a tone at a particular volume — the loudest possible. To understand what that means, it it is helpful to know that the sounds we are creating are represented within WebAudio as floating point PCM (Pulse Code Modulated) data. That just means it is a series of floating point numbers (samples), each representing a voltage at a particular time. When those values are ultimately converted into voltages to drive the speakers or headphones, the values 1.0 and -1.0 represent the maximum positive and negative voltages the hardware is designed to put out.



Our oscillator (since no different waveform is specified) produces a sine wave with a magnitude of 1.0, the largest magnitude it can have without clipping (exceeding the maximum voltage). OK, so what if we don’t always want the loudest possible tone?

An oscillator does not have a parameter to control its volume, so instead, we can insert a GainNode between the oscillator and the audio context’s destination. The gain control applies a specific multiplier to each sample value that passes through it, so (for instance) if we apply a gain of 0.5, that will scale our sine wave of magnitude 1.0 down to a magnitude of 0.5.

Don’t be confused that the word “gain” implies an increase. A “gain” can either a boost (for > 1.0) or cut (for < 1.0) the magnitude of a signal.

Now, let’s add a simple gain control to our tone generator. First, we’ll insert code for the gain input at the top of the page, right after the opening <body> tag as follows.

Next, change the setup code to add variables for the gain elements, create a gain node, and connect the gain node to the audio context destination.

Bind the gain value to a handler function right before the bindings for the play and stop buttons, and then add the gain handler to update the gain node value.

Finally, change the code in the playTone() function to connect each new oscillator to the gain node instead of directly to the context’s destination.

You might notice a couple of issues with this while playing around with this in the browser.

The first issue is that there is a click every time you adjust the gain while a tone is playing. This is because the sample value usually won’t happen to be 0 at the time of the change, and changing the gain will cause the value to make a sudden jump between one sample and the next when the gain changes. That is also why there’s a click when stopping a tone. The value suddenly jumps from whatever it is at some point in the waveform to 0.

We can solve the click problems my making a gradual changes in gain rather than an instantaneous changes. How to do that will be covered later in this series.

The second issue is that the amount of change in volume per step is very different at higher gains than at lower gains. There’s very little apparent change in volume between 0.9 and 1.0, but the change between 0.1 and 0.2 is pretty large. That is because the way we hear relative volume relates to the ratio between those volumes and not to the absolute differences in magnitude. The difference between 0.9 and 1.0 is 11.1%, but the difference between 0.1 and 0.2 is 100%.

You’ve probably encountered the term Decibel (dB) in relation to sound levels. The Decibel is a logarithmic unit that expresses relative sound level in terms more like how we truly experience it.

dB to Gain Ratio Graph

The next article in this series will cover how to change gain gradually rather than abruptly. Later in the series, we’ll also cover how to specify gain in terms of Decibels (exponential) rather than linear ratio units.