Making Music with The WebAudio API Part 4

At the end of part 3 of this series, we had an HTML document for a page with buttons to start and stop playing a tone, we could select a musical note pitch for the tone, and we could change the volume. There were also some things about it that were a little rough that we’d now like to improve.

Here’s what the HTML for our page looks like so far.

The next issue we’ll address is the fairly obvious click that we hear when stopping a tone or changing the volume while a tone is playing. This problem is caused by abruptly stopping playback or changing the gain at an arbitrary point in the waveform. The way to solve it is to make gain changes gradually rather than suddenly.

Let’s solve the problem with volume changes first because it’s the simpler case.

Our gain variable contains a GainNode, and the .gain property of a GainNode contains an AudioParam. Besides having a .value property that we can get or set, an AudioParam also has many other methods that we can utilize. The most suitable of those for our purpose is .linearRampToValueAtTime().

Note that we are not using .exponentialRampToValueAtTime() because, although that might sound more musical, the linear ramp is perfectly adequate here, and exponential cannot be used for ramping to a value of 0 which we want to be able to do.

In order for the first call to .linearRampToValueAtTime() to work properly, we must have previously set the audio parameter’s value using one of the scheduling methods, so the next thing to do is add the following right after the gain.connect( audioCtx.destination ); line.

Now we can implement ramp the volume changes by changing the changeGain() function to read as follows.

Note that the endTime parameter must contain a time relative to when our audio context was first started. That is why we add the 0.1 seconds ramp duration to audioCtx.currentTime.

Having addressed the clicks when changing volume, now let’s take care of the clicks when stopping notes. To do that, we’ll change the stopTone() function to read as follows.

Notice that we first set the gain to 1 at a specific time and then ramp to 0 ending at a time 1/20 of a second later. The ramp will start right after the previously scheduled value change is done, so the total ramp time will be 1/20 of a second. So why the initial delay of 1/20 of a second before setting the value to 1? Frankly, I don’t know why this is necessary, but I have found that if that delay is not given, then we don’t get the desired result and still hear a click when stopping the note. If you happen to know why, please share that in a comment on this article.

Finally, another 1/20 of a second after the end of the ramp, we stop the oscillator. We also move our button state toggling into the handler for an event that gets called after the oscillator has been stopped. That way, we don’t let the user try to start a new tone while we’re still in the process of stopping the previous tone.

The next article in this series will cover how to specify the volume/gain in terms of Decibels (exponential) rather than linear ratio units.

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.

unit-sine-wave-pcm

 

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.

Making Music with The WebAudio API Part 2

In Part 1 of this series, we created a page that lets a user click a button to play a tone and then click another button to stop playing the tone as follows…

Now, let’s make it possible to play and stop the note over and over again. First, we’ll try doing it the obvious way that does not actually work, modifying the playTone() and stopTone() functions to read as follows…

If you try to use this, you will find that nothing happens when you try to play the note after previously playing it and stopping it. If you have a browser console open, you’ll see an error something like “InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable”.

What this is telling us is that the oscillator object can only be used once. After it has been stopped, it can never be started again. What we need, then, is to create a new oscillator instance for each time that we want to start playing a note. We can do that by changing our JavaScript to read as follows.

Now you can successfully start and stop the tone as many times as you want.

So next, how can we play other notes besides Middle A? Each note has a particular frequency, so we could just have a big map of note values to frequencies. We ca do the math for that directly in JavaScript instead though.

In Western music, we are usually working with a Chromatic scale having twelve-tones per octave. The frequency of a note in one octave is always exactly double that of the same note in the next lower octave. For instance, A in octave 4 (Middle A) is 440 Hz and A in octave 5 is 880 Hz. A Chromatic scale means that the ratio between any 2 successive note frequencies is always the same. In order to get from one note to the one an octave above it by applying the same ratio 12 times, we need each ratio to be the twelfth root of 2 which can also be described as 2 to the power of 1/12.

Knowing that, we can number our notes starting at A4 = 440Hz being note number 0, adding 1 to that for each semitone higher or subtracting 1 for each semitone lower that we want to produce.

To incorporate this knowledge into our demo, we first add a note number input to our page right before the buttons.

Next, we modify the playTone() function to read as follows.

Now, if you enter a number into the “Note number” input before clicking the “Play Tone” button, you will hear a tone with a pitch that number of semitones higher (for positive numbers) or lower (for negative numbers) than Middle A.

The next article in this series will discuss how to control the volume. Later in the series, we’ll discuss how to eliminate the annoying click that you might have noticed when stopping tones.

Making Music with The WebAudio API Part 1

The WebAudio API gives us a way to use JavaScript to make and manipulate audio within a Web page in some very powerful ways. This series of articles demonstrates how we can use WebAudio to synthesize music.

The following example lets a user click a button to play a tone and click another button to stop it.

In this example, the first important thing that happens is creating a new AudioContext. Methods of that object will be used to create other kinds of WebAudio objects and (crucially) it has the .destination property, which is what we connect an output to so that we can hear the sound that we’re creating.

Next, we call the context’s .createOscillator() method to create an OscillatorNode object. That is the object that will produce the tone.

In order to specify what pitch of tone our oscillator will produce, we then set its frequency to 440. The unit of measurement for that value is Hz (oscillations per second) and 440 Hz is the typical reference frequency for a middle A (see Note Frequencies).

You will notice that we do not set the frequency of the oscillator by simply assigning a value to oscillator.frequency. Instead, we assign it to oscillator.frequency.value. That is because oscillator.frequency refers to an AudioParam object (see AudioParam) for the frequency and not to the frequency value itself.

Now that we have a context and a configured oscillator, we need to connect the output of the oscillator to the context’s destination. That is what oscillator.connect( audioCtx.destination ); does.

Before it will produce any sound, the oscillator now needs to be started, so when the user clicks the “Play Tone” button, we make a call to
oscillator.start(). We also enable the “Stop Tone” button.

Finally, when the user clicks the “Stop Tone” button, we call oscillator.stop() to make the oscillator stop producing its tone.

The next article in this series will discuss how to play a note more than once and how to play notes of various musical pitches.

How To Compute 1+2+…+n and How to Remember How

I was recently writing a test for an object containing a map of currency amounts for months by month number. I needed to verify that each value was exposed through a month-specific attribute and that it exposed the sum of values across all the months through another attribute.

For test data, I chose the following map:

Obviously, those 12 thousands add up to 12,000, but what is the contribution of those 1–to–12 parts? I could have just added the numbers together, but there’s an easier way.

That easier way is the formula 12 ⨉ 13 / 2 or more generally, n ⨉ (n + 1) / 2, and in the case above, that’s 78, but how do I remember that formula? This is something I need to know somewhat often, but not quite often enough to simply have the formula memorized. The way that I remember how to re–create this formula quickly when I need it is with a nifty visual mnemonic that I’d like to share. For this example, we’ll say that n is equal to 4.

half-of-4-by-5

The black squares (the ones we count) form a triangle, and the white squares (the ones we don’t count) form an identical triangle rotated 180 degrees. If we stick those triangles together as shown, we get a 4 x 5 rectangle, and since each triangle in the rectangle has the same number of squares, that means half of the squares in the rectangle are black.

Curious Effects of Different Ruby Class & Module Nesting Styles

Typically, in Ruby, we see modules and classes nested by having one declaration inside of the the other.

When the outer class or module has previously been declared, however, we can also declare the nested one directly using its fully qualified name, and it is not uncommon to see that done.

On the surface, these seem to be 2 ways to do exactly the same thing, and for the most part they are. They are not doing exactly the same thing, however, and that can result in some interesting side effects. We start to see those effects when methods methods defined within our declarations make reference to constants.

As you can see, each of the instance methods of Foo::Bar  is accessing constants from a different level of nesting.

What’s going on is that the nesting with respect to an executing method belongs to the method itself and not the class or module on which it is defined. The source of that nesting is nesting of class  and module  code blocks at the point where the method was defined.

The side effects of this can get positively weird.

Say what? The hello  instance method cannot even reference its own class using an unqualified name? That is because the Bar  constant belongs to the Foo  module, but since the method was not defined within a module Foo  block, its constants (including Bar ) are not implicitly available.