Thursday, December 12, 2019

My discovery of Web Audio

It seems most modern web browsers now come packed with a built-in sound-synthesizer. You control this Web Audio API directly using Javascript. You can make it play a simple waveform (sine, sawtooth, triangular, square or custom) that plays as long as you want.

Using this you can, for example, display a playable piano on your webpage.

I've used it to create the tones on my harmony page which plays samples of chord tunings, chord progressions, intervals and scales. It replaces the pre-recorded audio files, and the MIDI files that predated those.

Now I found the additional ability of Web Audio to load a sound file, decode it, and put it in a buffer where it may be played at an exact time. This may sound redundant in a browser that already has the <audio> node. But the Web Audio feature plays the sounds instantly, while the <Audio> node seems to be bedeviled by a slight delay before it begins playing. In contexts like a video game this matters. This also makes background music loop seemlessly rather than having a slight gap before the music repeats.

In Web Audio, you create a "Source" node, which may be an oscillator (the one which generates its own waveform), or a buffer (which stores preloaded audio). You can play it simply by connecting this node to a "Destination" (which is typically your sound output on your computer) and starting it. If you want to be able to change the volume, you instead connect the Source node to a "Gain" node, and the Gain node to the Destination node. You set the volume in the Gain node. The sound’s sample value is multiplied by the gain’s value. This means that the default gain value of 1 plays the sound unmodified. 0 is silent. Using values greater than 1 results in overloaded sound, with clipping. Negative values invert the waveform (which by itself doesn’t affect how it sounds).

I had a recent bug where the 1 seemed to play the sound normally, but 0 just played it a little quieter. And -1 made it silent! That didn't make any sense to me, until I found the bug. In addition to adding the Gain to my chain of nodes:

Source -> Gain -> Destination

I forgot to delete the original statement connecting the Source directly to the Destination:

Source -> Destination

So I guess the sound arrived at the destination twice, both directly from the Source, and through the Gain control as well. I'm guessing the sound I heard at a volume of 1 may have been louder than it would otherwise be since it received the same audio sample twice. 0 muted the audio going through Gain, but the audio using the direct route played unmodified. Thus the slighly quiter sound. Setting the volume to -1 gave the Gain-controlled sound a negative amplitude, which cancelled out the unmodified audio, creating silence. That's my take on what was going on.

So if you wish to add a Gain node to your audio chain, remember to delete the Source -> Destination code before using Source -> Gain -> Destination.

It's true a computer does exactly what you tell it to. But if you give ambiguous instructions, the results will be unpredictable.

No comments:

Post a Comment