Javascript For Sound Artists PDF
Javascript For Sound Artists PDF
Javascript For Sound Artists PDF
for
Sound Artists
Learn to Code with the Web Audio API
JavaScript for
Sound Artists
Learn to Code with the Web Audio API
This book contains information obtained from authentic and highly regarded sources. Reasonable efforts have been made to publish
reliable data and information, but the author and publisher cannot assume responsibility for the validity of all materials or the
consequences of their use. The authors and publishers have attempted to trace the copyright holders of all material reproduced in
this publication and apologize to copyright holders if permission to publish in this form has not been obtained. If any copyright
material has not been acknowledged please write and let us know so we may rectify in any future reprint.
Except as permitted under U.S. Copyright Law, no part of this book may be reprinted, reproduced, transmitted, or utilized in any
form by any electronic, mechanical, or other means, now known or hereafter invented, including photocopying, microfilming, and
recording, or in any information storage or retrieval system, without written permission from the publishers.
For permission to photocopy or use material electronically from this work, please access www.copyright.com
(http://www.copyright.com/) or contact the Copyright Clearance Center, Inc. (CCC), 222 Rosewood Drive, Danvers, MA 01923,
978-750-8400. CCC is a not-for-profit organization that provides licenses and registration for a variety of users. For organizations
that have been granted a photocopy license by the CCC, a separate system of payment has been arranged.
Trademark notice: Product or corporate names may be trademarks or registered trademarks, and are used only for identification
and explanation without intent to infringe.
Preface
Acknowledgment
3. Operators
What Are Operators?
Assignment Operators
Assignment
Addition Assignment
Subtraction Assignment
Multiplication Assignment
Division Assignment
Modulo Assignment
The Boolean Data Type
Comparison Operators
Equality Operator
Strict Equality Operator
Greater Than and Less Than Operators
Greater Than or Equal to Operator
Less Than or Equal to Operator
Not Equal to Operator
Strict Not Equal to Operator
Logical Operators
The Logical AND Operator
The Logical OR Operator
The NOT Operator
Summary
5. Functions
Functions—A Simple Example
Parts of a Function
Function Expressions
Abstracting Oscillator Playback
A Working Effects Box Example
The Arguments Object
Function Scope
Why You Should Always Declare Your Variables with var
Variable Hoisting
How Hoisting Affects Functions
Anonymous Functions
Closures
What Is a Closure?
Callback Functions
Working with JavaScript’s Built-In Callback Functions
filter()
map()
Recursion
Summary
6. Objects
JavaScript Data Types
Looping through Objects
When to Use Objects Rather Than Arrays
How to Check If an Object Has Access to a Particular Property or Method
Cloning Objects
Prototypal Inheritance
The "this" Keyword
The bind Function
Summary
20. Time
The Timing Clock
The start Method
Looping Sounds
Update Your Audio Loader Library
Changing Audio Parameters over Time
The Audio Parameter Methods
The setValueAtTime Method
The exponentialRampToValueAtTime Method
The linearRampToValueAtTime Method
The setTargetAtTime() Method
The setValueCurveAtTime() Method
Summary
Index
Preface
Learning to program can be daunting, and we want to be the first to congratulate you for taking on
the challenge! Second, we want to thank you for choosing this book.
Make Connections
Generally, it is easier to learn new things by making associations and connections to areas that
you are already familiar with. If you have ever programmed a synth or a MIDI sequencer, then you
have already done a form of programming. The contents of this book are designed to be a bridge
that connects a world you are (presumably) familiar with (sound and audio technology) to a topic
you are less familiar with—JavaScript and programming. We suggest that you tap into whatever
has drawn you to sound art while learning the material in this book.
Make It Habitual
Programming is all about learning a bunch of little things that combine to make big things. The
best way to learn a lot of little things is through repetition and habit. One way to do this is to
simply accept programming as a new part of your lifestyle and do a little bit (or a lot) every day.
Keep Going
Our final piece of advice is to simply stick with it.
Best of luck!
If you have any questions or comments, you can find us at:
http://www.javascriptforsoundartists.com
William Turner
Steve Leonard
Acknowledgment
What Is a Program?
A program is any set of instructions that is created or followed. In this book, we focus on writing
computer programs, which are lists of instructions that a computer carries out. These instructions
can be written and stored in various forms. Some of the first modern computers used punched
cards, switches, and cables. Early analog music synthesizers were a type of computer that used a
patchbay style interface to manually allow a programmer to create specific sounds.
What Is JavaScript?
JavaScript is a multipurpose programming language initially created to aid developers in adding
dynamic features to websites. The language was initially created in 11 days and released in 1995
by a company called Netscape. Developed by Brendan Eich, its original release name was
LiveScript. When Netscape introduced support for the language in its browser, LiveScript was
renamed JavaScript. Although JavaScript is similar in name to the Java programming language,
they are completely unrelated. Today, JavaScript is used in everything from robotics to home
automation systems.
JavaScript is used to add interactive responses to user input. Every time a user clicks, scrolls,
taps, moves the mouse cursor, types, or performs an interactive event, JavaScript code can be
triggered to change the page in some manner. The JavaScript language was initially designed to
perform these functions within the context of designing websites and applications.
</body>
<!--____________________________________________END APP-->
</html>
5. Inside the web audio template folder, create another folder called css.
6. In Sublime Text, create a new file by going to the File menu and click New. Save this file in
your css folder as app.css. Leave the contents of this file empty.
7. In the web audio template folder, create another folder called js.
8. Create a new empty document in Sublime Text, then type "use strict"; (including
quotations and semicolon) at the top of it and save it as app.js in the js folder you just
created. This places your JavaScript in strict mode. Strict mode is a restrictive form of
JavaScript that enforces better programming practices. All JavaScript code examples in this
book will assume you have strict mode enabled.
You are now going to add a few extensions to Sublime Text that will make working with the
editor easier in the long term. To do this, you must first download and install the package manager
plug-in. Go to the following link and follow the directions on the left side of the window:
https://packagecontrol.io/installation. When done, close the console by entering the keys: Ctrl + `
(apostrophe, on the key with the ~).
1. In the Sublime Text menu, go to Tools > Command Palette, and in the form field that
appears, type install. You should see an option menu appear that says Package control:
Install package. Click this menu option.
2. Another form field with a series of options appears. This form field allows you to search and
explore various plug-ins for Sublime Text. You are now going to install a plug-in that allows
you to create a local web server that will be necessary when working with audio files. In the
form field, type Sublime server. A list of search results should appear. Click the first
one. Look at the bottom of the Sublime Text window, and you should see “installing” in
small text. When this process is done, quit and restart Sublime Text. We will cover the
specifics of the web server in a later chapter. But rest assured that this setup will be time
well spent. To verify that the plug-in is installed, go to Tools > SublimeServer > Start
SublimeServer. Open your web browser to http://localhost:8080/, and it should display
SublimeServer at the top of the page.
3. This last plug-in you are going to install lets you open HTML files in Chrome from within
Sublime Text. To install the View in Browser plug-in, go to Tools > Command Palette and
in the form field that appears, type install. Click Package control: Install package.
Then do a search for View in Browser, and select the first option that appears. Once the
installation is done, you will need to go to the following menu to set up the plug-in to work
with Chrome.
3. On line 6, remove the <!-- and --> characters and type the word test in between the two
elements. The result should look like this: <tabTrigger>test</tabTrigger>.
4. Save the file in the default directory that appears and call it test.sublime-snippet.
5. Open your index.html file in Sublime Text, type the word test, then tap the TAB button on
your keyboard. The phrase "this is a test snippet" should appear in the editor.
After you verify that the Hello Sound program works, close the browser. You just wrote your
first Web Audio API program!
The code you just ran is a basic oscillator generation and playback script. The first line in the
script is called the “Audio Context” and this tells the browser that you are using the Web Audio
API. The next line of code creates an oscillator. The third line of code assigns a waveform type to
the oscillator, whereas line four connects the oscillator to a virtual audio output called the
destination, which is analogous to the speakers of your computer. The last line starts the
oscillator playing. We will cover detailed operation of the Web Audio API in future chapters.
First, though, we need to cover the basics of the JavaScript language.
Variables
One of the first steps in writing a program is understanding variables and variable assignment.
Variables are word forms that are used to store data. For example:
var waveformType = "sawtooth";
The variable here is named waveformType. This is preceded by the var keyword. You
always specify the var keyword prior to declaring the variable. Declaring a variable means you
are creating a new variable. After the var keyword, you type a space and give a name to your
variable. Variable names are typically a reflection of something they represent. In this case, the
variable is being used to describe a type of oscillator waveform and so is named
waveformType. You probably noticed the odd capitalization of the word “type” in
waveformType. The convention of capitalizing words to distinguish them within variable
names is called camel case. This convention is used because variable names cannot contain white
space to separate them. If you rewrote the variable in the following manner, you get an error:
var waveform type = "sawtooth"; //____returns an error
Type the above code into the app.js file of your hello_sound template. Launch Chrome and
open the developer tools (Windows: Ctrl + Shift + J or Mac: Command + Option + J). Inside the
console tab, you should see an error similar to the one in the following image.
The text in gray is the actual error and is identified as a syntax error. To the right of the error,
you can see the file and the line number where the error occurred. This number corresponds to the
line number in your file, which might differ from the one in the image. After you see the error,
remove the line you added that is causing the error in app.js and save the file.
After you declare and name a variable, you can assign some data to it. You use the assignment
operator “=” to do this.
It is important to understand that in JavaScript the “=” symbol is not called the equal sign and
its functionality does not mean equal to. The “=” symbol indicates assignment, so it is called the
assignment operator. The value on the right side of the assignment operator contains the data you
want to assign to the variable name on the left side. In the following example, the string
"sawtooth" is assigned to the variable waveformType.
var waveformType = "sawtooth";
When you assign a string of words to a variable, you must place them between quotation
marks. The resulting data type is called a string. Data types represent the types of data that you
can use in your program. Different programming languages have different data types. JavaScript
has six data types, and one of these is the string data type (see Chapter 6 for a list of JavaScript
data types).
After you assign data to your variable, you must end the variable declaration with a semicolon.
In summary, there are five parts to a variable declaration:
■ The var keyword
■ The variable name
■ The assignment operator
■ The data you wish to assign to the variable
■ The closing semicolon
You can assign multiple variables at once using the following syntax:
var osc1 = 1200,
osc2 = 1300,
osc3 = 100;
In some cases, you might want to declare a variable and not assign data to it, as in the
following example:
var waveformType;
If you do this, JavaScript automatically assigns undefined to it. You can also assign
undefined explicitly like this:
var waveformType = undefined;
The keyword undefined is another JavaScript data type. Notice that undefined is not
enclosed in quotation marks because it is not a string but represents a data type.
null
The primitive value null is similar to the primitive value undefined. Both can act as a
placeholder for empty variables. When the typeof operator (discussed later in this chapter) is
used to determine the type of null, the result is object. This is not what you might expect and
is a flaw in the language. The correct returned value should be null. Because of this, we suggest
that you never use null and always use undefined.
All the characters from the // to the end of the line are ignored by the computer.
Replace the osc.type assignment with the waveformType variable like this:
var audioContext = new AudioContext();
var waveformType = "sawtooth"; //___added variable
var osc = audioContext.createOscillator();
osc.type = waveformType; //__Assigned it to our oscillator type
osc.connect(audioContext.destination);
osc.start(audioContext.currentTime);
Launch your web browser, and instead of hearing a sine waveform, you should hear a sawtooth
waveform.
In this example, the following declarations assign values to variables that represent other
waveform types.
var audioContext = new AudioContext();
//___ 4 variables that represent oscillator waveforms
var saw = "sawtooth";
var sine = "sine";
var tri = "triangle";
var square = "square";
//___ A variable intended to contain one of these waveforms
var currentWaveform = undefined;
currentWaveform = square;
//_____________________________Start of oscillator
var osc = audioContext.createOscillator();
osc.type = currentWaveform; // Assigned it to our oscillator type
osc.connect(audioContext.destination);
osc.start(audioContext.currentTime);
Each of the four new variables contains a string that represents an oscillator waveform type.
The square variable is assigned to the currentWaveform variable in the following line:
currentWaveform = square;
Notice that no new declaration is required for the currentWaveform variable to assign
(and replace) whatever was previously assigned to it. The new data on the right side of “=” is
assigned to currentWaveform. If you launch your web browser, you will hear a square wave
play. In programming, being able to overwrite variables in this manner is referred to as
mutability (changeability), and we say that variables are mutable. The opposite of this is called
immutability.
console.log()
When programs begin to get big, it can be difficult to know what value is assigned to a variable at
any given moment. One way you can find out is by using a built-in feature called
console.log().
The way you do this is by typing console.log() into your code at the point where you
want to check a given variable’s assignment. You then place the variable name inside the
parentheses.
To see what the currentWaveform variable has as its assignment, you do this:
var audioContext = new AudioContext();
//Added 4 variables that represent oscillator waveforms
var saw = "sawtooth";
var sine = "sine";
var tri = "triangle";
var square = "square";
var currentWaveform = undefined;
currentWaveform = square;
console.log(currentWaveform); //___ square
//____________________________________Start of oscillator
var osc = audioContext.createOscillator();
osc.type = currentWaveform; // Assigned it to our oscillator type
osc.connect(audioContext.destination);
osc.start(audioContext.currentTime);
Launch Chrome, open the developer tools and click the console tab; you will see the output of
our console.log().
One thing to remember is that because variables can have different values at different times,
the output of console.log() depends on where it is placed in the program. If you modify the
last example and place console.log() immediately after the currentWaveform
variable, which has undefined assigned to it, then undefined is output to the log.
//__________A variable intended to contain one of these waveforms
var currentWaveform = undefined;
console.log(currentWaveform); //______results in "undefined"
currentWaveform = square;
So far we’ve mentioned three of the six data types in JavaScript. The first was string, the
second was undefined, and the last was null.
Before we go further, let’s explore the string data type a bit more in depth.
String
As we already discovered, strings are denoted by quotation marks. The variable below is a
string:
var oscillator = "square";
You can manipulate strings in different ways. One of the most common is by combining
multiple strings into one string. This is called concatenation, and it works by using the plus sign
(+) like this:
var oscillator = "saw" + "tooth";
console.log(oscillator); // sawtooth
Here is another example of concatenating two variables and storing them in a new variable.
var phrase = "This sound is an ";
var soundType = "oscillator";
var sentence = phrase + soundType;
console.log(sentence); // "This sound is an oscillator".
If you want to get the number of characters in a string, you can use what is called the length
property like this:
console.log(myFavoriteSynthCompany.length); // 33
The output of the length property includes the white-space characters of the string.
toLowerCase()
This method changes all the characters in a string to lowercase.
var oscillator = "SAWTOOTH";
oscillator.toLowerCase(); // "sawtooth"
You do not need to immediately memorize how each of these methods works, but it’s a good
idea to know about them. This way, when you do need to implement any of the functionalities they
provide, you know which tool to reach for. If you would like to explore more string methods, a
good resource is the Mozilla Developer Network at: https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/String.
Let’s go through each one of these and explain how to use them.
charAt()
This method gets a character at any given index value within a string. For example, if you have
the string "oscillator-1" and want to know what the second letter of this string is without actually
looking at it, you can do this:
var sound = "oscillator";
console.log(sound.charAt(1)); // "s"
Now you might be wondering why charAt(1) returns “s” and not "o". The reason is that the
count begins at zero. So, to get the first letter do this:
console.log(sound.charAt(0)); // "o"
replace()
This method finds a group of characters in a string and replaces them with another string. If you
want to replace an entire word, you can do it like this:
var myFavoriteSynthCompany = "My favorite synth company is
Moog. Moog is great!";
var myNewFavoriteSynthCompany = myFavoriteSynthCompany.
replace("Moog","Dave Smith Instruments");
console.log(myNewFavoriteSynthCompany); /*My favorite synth
company is Dave Smith Instruments. Moog is great!*/
As you probably noticed, when using the replace method in this manner it only replaces the
first instance of the word you select. To replace all instances of the word, you need to use the
following syntax to globally replace them in the string.
var myFavoriteSynthCompany = "My favorite synth company is
Moog. Moog is great!";
var myNewFavoriteSynthCompany = myFavoriteSynthCompany.
replace(/Moog/gi,"Dave Smith Instruments");
console.log(myNewFavoriteSynthCompany); /*My favorite synth company
is Dave Smith Instruments. Dave Smith Instruments is great!*/
The g stands for global and the i denotes case insensitivity. If you want the string
replacement to be case sensitive, you use a g and omit the i. These characters are part of a
pattern-matching language for string data called regular expressions. Regular expressions are an
advanced topic that will not be covered further in this book.
slice()
This method extracts part of a string.
var oscillator = "sawtooth";
var sound = oscillator.slice(0,3);
console.log(sound); // saw
Like charAt(), slice() works on a zero-based index. This means the first character is
always zero. The slice method takes two values: a beginning index value and an ending index
value. When a method takes values, they are called arguments. The charAt() method takes one
argument. The slice() method takes two arguments. The slice method’s first argument is where
the slice starts, and this value is included in the slice. The second value is where the slice ends
and is noninclusive. This means all the characters up to, but not including, the second value are
included in the slice.
If you want to get the last value of a string, you can combine the length property with the
charAt() method. This allows you to retrieve the last character in a string in a manner that
doesn’t require you to know how long the string is. The code shows an example of this. The
reason you subtract 1 from the length property is because the length property begins
counting at one, whereas charAt() begins counting at zero. Therefore, you subtract 1 from the
length property to compensate for the offset.
var sound = "oscillator-1";
var oscNumber = sound.charAt(sound.length - 1);
console.log(oscNumber); // 1
Numbers
In JavaScript, numbers are a distinct data type. Below is a variable named frequencyValue,
and it is assigned a number of 200. It is then assigned to the oscillator’s pitch. If you place the
code below in a new JavaScript file and run it, you will hear an oscillator play at a frequency of
200 Hz. Modify the number value assigned to the frequencyValue variable and launch the
code to hear the oscillator play at different pitches.
var audioContext = new AudioContext();
var frequencyValue = 200; //___Create variable frequencyValue
var waveform = "sawtooth";
var osc = audioContext.createOscillator();
osc.type = waveform;
//_____ assign it to the oscillators pitch
osc.frequency.value = frequencyValue;
osc.connect(audioContext.destination);
osc.start(audioContext.currentTime);
Unlike strings, numbers do not use quotation marks. In fact, if you did use a number with
quotation marks, its data type would not be number, it would be string.
Here’s an example:
var oscillators = "6";
var polyphony = 6;
console.log(typeof oscillators); // string
console.log(typeof polyphony); // number
You can do basic math with numbers using the following symbols. These symbols are called
arithmetic operators.
+ Addition
− Subtraction
* Multiplication
/ Division
% Modulo
The last symbol (%) might be new to you, and it is pronounced moj-uh-loh. The purpose of
this symbol is to output the remainder of a division. So, for example:
console.log(12 % 9); // This equals 3
The precedent rules of algebra also apply. If you wrap a calculation in parentheses, the
calculation inside the parentheses is performed first.
Examples of Precedence
var oscillator1 = 1000;
var oscillator2 = 100;
var oscillator3 = 20;
var combinedOscillator = oscillator1 +(oscillator2 * oscillator3);
console.log(combinedOscillator); // 3000
If you want to do more elaborate calculations, JavaScript has a built-in tool called the Math
object, which allows you to use a collection of math methods to manipulate numbers.
So, for example, if you want to round a decimal number to its nearest integer, you can use
Math.round() like this:
Math.round(1000.789); // outputs 1001
Math.random()
The random method creates a random number between zero and one.
var randomNumber = Math.random();
console.log(randomNumber); // example: 0.019790495047345757
Math.abs()
The abs method allows you to get the absolute value of a number.
var num = Math.abs(-100);
console.log(num); // 100
This is useful for finding the difference between numeric variables of unknown values.
var a = 1000;
var b = 5000;
console.log(Math.abs(b - a)); // 4000
Number-to-String Conversion
If you want to convert between numbers and numeric strings, you can use the following
techniques.
To convert a string to a number, place the plus symbol (+) before the string like this:
var numericString = "120";
var num = +numericString; // plus symbol
console.log(num); // 120
console.log(typeof num); // number
If you want to convert a number to a numeric string, concatenate the number with an empty
string like this:
var num = 80;
var numericString = num + "";
console.log(numericString); // 80
console.log(typeof numericString); // string
If you attempt to do a math operation using nonnumeric values, sometimes you will receive a
returned value of NaN. This stands for not a number. Here is an example of attempting to add two
values in which one value is a number and the other is not.
var osc1 = undefined;
var osc2 = 200;
console.log(osc1 + osc2); // NaN
Arrays
Arrays are a construct that holds multiple pieces of data. You can think of them as variables that
hold more than one item. Arrays are expressed using brackets, where each item is separated by a
comma. Each item in the array is designated an index number with the first item starting at zero.
var waveforms = [ ]; // empty array
var waveforms = ["square", "sawtooth", "triangle", "sine"]; //
array with some data
If you want to access any of these data, you can use the following notation:
waveforms[0]; // square
waveforms[1]; // sawtooth
waveforms[2]; // triangle
waveforms[3]; // sine
waveforms[4]; // undefined (no data)
If you want to know how many items are inside an array, use the length property like this:
var waveforms = ["square", "sawtooth", "triangle", "sine"];
waveforms.length; // 4
Arrays come with built-in methods that you can use to manipulate the data in them. A full list of
these are available at the Mozilla Developer Network at this URL:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array. We
are only going to go over a handful of these and they are:
push()
This method adds items to the end of an array.
var synthFrequencies = [5000, 1000, 500];
synthFrequencies.push(100); /*This places a new item at the end of
the array*/
console.log(synthFrequencies); // [5000, 1000, 500, 100]
You can use the push method to add multiple items at once.
var synthFrequencies = [5000, 1000, 500];
synthFrequencies.push(100, 50, 30);
console.log(synthFrequencies); // [5000, 1000, 500, 100, 50, 30]
pop()
This method removes a single item at the end of an array.
var synthFrequencies = [5000, 1000, 500];
synthFrequencies.pop();
console.log(synthFrequencies); // [5000, 1000]
If you want to capture the last item you removed from an array in a variable, do this:
var synthFrequencies = [5000, 1000, 500];
var lastItem = synthFrequencies.pop();
console.log(lastItem); // 500
shift()
This method removes an item from the beginning of an array.
var synthFrequencies = [5000, 1000, 500];
synthFrequencies.shift();
console.log(synthFrequencies); // [1000, 500]
If you want to capture the first item you removed from an array in a variable, do this:
var synthFrequencies = [5000, 1000, 500];
var firstItem = synthFrequencies.shift();
console.log(firstItem); // 5000
unshift()
This method adds new items to the beginning of an array.
var synthFrequencies = [5000, 1000, 500];
synthFrequencies.unshift(7500, 6000);
console.log(synthFrequencies); // [7500, 6000, 5000, 1000, 500]
concat()
This method merges multiple arrays together into one array.
var drumMachines = ["MPC", "Machine", "TR 808"];
var keyboards = ["Juno", "ARP", "Jupiter"];
var percussion = ["vibraphone", "bongos"];
var stringed = ["guitar", "bass", "harp"];
var instruments = drumMachines.concat(keyboards, percussion,
stringed);
console.log(instruments); /* ['MPC','Machine','TR 808','Juno','ARP',
'Jupiter','vibraphone','bongos','guitar','bass','harp'] */
Summary
In this chapter, you learned about variables, comments, numbers, strings, and arrays. In the next
chapter, you will learn about various assignment and logical operators.
3 Operators
You learned about the basic assignment operator (=) and some of the arithmetic operators in the
previous chapter. In this chapter, we are going to explore other assignment operators, as well as
comparison operators, that allow you to determine the relationship between variables and values,
such as whether they have the same value. We will also explore the Boolean data type, which has
either a true or a false value that can be assigned to variables or is the result of a comparison
operation.
Operators fall into arithmetic, assignment, or logical categories. The arithmetic operators that
we covered in the previous chapter are used with numbers. The assignment operators are used to
assign values to variables. The logical operators are used to compare two values and return a
true or false value based on the result of the comparison.
Assignment Operators
Assignment operators are used to assign data to variables. Here is a list of assignment operators:
Assignment
This operator assigns a value to a variable.
var osc = 100;
With assignment operators, you can also assign variables to other variables.
var osc1 = 100;
var osc2 = osc1;
console.log(osc2); // 100
Addition Assignment
This operator increments a numeric variable or appends a string to a variable. In the following
example, an oscillator is assigned a value of 100 and then incremented by 100 to give it a value
of 200.
var osc = 100;
osc += 100;
console.log(osc); // 200
To demonstrate the use of the addition assignment operator, the following code sets an ever-
increasing frequency change to an oscillator and you can listen to the effect. A method called
setInterval() is defined, although the specifics of setInterval() are not important at
this time. What is important is understanding that the addition assignment operator is incrementing
the frequency value by 100 every 0.5 seconds when setInterval() is called.
var audioContext = new AudioContext();
var osc = audioContext.createOscillator();
osc.frequency.value = 300;
osc.connect(audioContext.destination);
osc.start(audioContext.currentTime);
setInterval(function(){
osc.frequency.value += 100; /*____Increment frequency value by
100 every 0.5 seconds*/
console.log(osc.frequency.value); //_____View change
},500); //________________________500 milliseconds is 0.5 seconds
When you use the addition assignment operator with a string, the string you supply is
concatenated with the variable. Here is an example:
var keyboards = "";
keyboards += "Korg ";
keyboards += "Yamaha ";
keyboards += "Kurzweil ";
console.log(keyboards); // Korg Yamaha Kurzweil
Subtraction Assignment
This operator is used to decrement a numeric variable.
var osc = 500;
osc -= 100;
console.log(osc); // 400
Multiplication Assignment
This operator multiplies a variable with a value and assigns it to the variable.
var osc = 200;
osc *= 2;
osc *= 2;
console.log(osc); // 800
Division Assignment
This operator divides a variable by a value and assigns it to the variable.
var osc = 200;
osc /= 2;
osc /= 2;
console.log(osc); // 50
Modulo Assignment
This operator divides a variable by a value and assigns the remainder of that division to the
variable.
var osc = 200;
osc %= 150;
console.log(osc); // 50
Boolean values can also be the result of the comparison operators described below or used in
conditionals statements, which we will cover in the next chapter.
Comparison Operators
Comparison operators are used to compare two variables or values. They output a true or
false value depending on whether the variables or values are similar or different from one
another in some way. The similarity or difference being tested for is dependent on the operator
used. So, for example, if you test whether two values are the same using the strict equality
operator (= = =) and they are not the same, the resulting value is false. There are eight
comparison operators.
Equality Operator
This operator checks whether the left operand is equal to the right operand. It then returns a
Boolean value to represent the outcome of the comparison.
200 == 200; // true
"200hz" == "200hz"; // true
var osc1 = "200hz";
var osc2 = "200hz";
console.log(osc1 == osc2); // true
The equality operator can be a bit tricky because it attempts to do a data type coercion before
comparing operands. Data type coercion occurs when the code interpreter (in our case the web
browser) attempts to convert one data type into another. In the following example, we compare a
number and a numeric string. JavaScript tries to convert the string to a number before doing the
comparison. If the string is a numeric string, the conversion is successful, and the comparison is
performed. In this case, the result of the comparison is the Boolean value true because the
numeric string “200” is successfully converted to the value 200, which matches the value of
osc1.
var osc1 = 200;
var osc2 = "200";
console.log(osc1 == osc2); // true
The greater than and less than operators do data type coercion as shown in this example:
600 > "500" // true
600 < "500" // false
The greater than or equal to operator does data type coercion as shown in this example:
300 >= "300" // true
The less than or equal to operator does data type coercion as shown in these examples:
300 <= "300" // true
300 <= "500" // true
300 <= "200" // false
The not equal to operator does data type coercion as shown in this example:
"300" != 300 // false
Another way to look at this code is that, if a value is not false, then it is true, and if its value
is not true, then it is false.
In JavaScript, there are six values that evaluate to false. They are the following:
false
""
null
undefined
0
NaN
Summary
In this chapter, you learned about JavaScript assignment and logical operators, the Boolean data
type, and what values evaluate to false. In the next chapter, you will learn to leverage these
tools using two new concepts: conditionals and loops.
4 Conditional Statements and Loops
Conditional statements and loops are two of the most widely used constructs in programming.
Conditional statements allow your program to make choices based on a set of criteria. Loops use
repetition, allowing your program to complete many tasks quickly.
Conditional Statements
To create programs that do more than basic calculations or print text, they must be able to make
decisions. You can program these decisions by using conditional statements. Conditional
statements check if a value is true or false and then execute a branch of code based on this
condition. We are going to go over the following three conditional statements:
■ if
■ switch
■ ternary
The if Statement
The syntax of an if statement consists of the if keyword, a pair of parentheses, and two curly
braces. This is what an empty if statement looks like:
if(){
}
To use an if statement, you place a value or condition inside the parentheses and some code
to execute inside the curly braces. If the condition inside the parentheses evaluates to true, the
code inside the curly braces is executed. If the condition evaluates to false, no action is taken and
the code inside the curly braces is skipped. In the following code, an if statement is used to
check if an oscillator frequency is set to 80 Hz prior to play start. If it is, the oscillator plays; if it
is not, the code inside the curly braces is ignored and the oscillator does not play.
//___________________________________BEGIN Setup
var audioContext = new AudioContext();
var osc = audioContext.createOscillator();
osc.type = "sawtooth";
osc.frequency.value = 80;
osc.connect(audioContext.destination);
//___________________________________END Setup
//___________________________________BEGIN Check frequency
if(osc.frequency.value === 80){
osc.start(audioContext.currentTime);
}
//___________________________________END Check frequency
If statements can also have an optional else branch that executes if the initial condition
evaluates to false. In the following code, the if statement checks to see if frequency.value
is 100 Hz. If this condition is true, the oscillator begins to play. If this condition is false, the
else branch executes, assigns frequency.value to 50 Hz, and starts the oscillator playing.
//____________________________________BEGIN Setup
var audioContext = new AudioContext();
var osc = audioContext.createOscillator();
osc.type = "sawtooth";
osc.frequency.value = 200;
osc.connect(audioContext.destination);
//____________________________________END Setup
//____________________________________BEGIN Conditional
if(osc.frequency.value === 100){
//__evaluates to false
osc.start(audioContext.currentTime);
}else{
//__So this plays
osc.frequency.value = 50;
osc.start(audioContext.currentTime);
}
//____________________________________END Conditional
Suppose you want to check for more than two conditions and do something different for each
one, you can do this by creating an if statement with multiple else if branches in sequence.
The final else statement catches all conditions that were not met along the way. An empty
example looks like this:
if(){
}else if(){
}else{
In the following working example, the code executes and checks to see if osc.type is set to
"sine". If this condition evaluates to false, the else if branch runs and checks if the
oscillator type is set to "sawtooth". This evaluates to true, and the oscillator starts playing. If
osc.type is not set to "sine" or "sawtooth" (in other words, if both conditions evaluated
to false), then the result is execution of console.log(), which outputs “no condition met.”
var audioContext = new AudioContext();
var osc = audioContext.createOscillator();
osc.type = "sawtooth";
osc.connect(audioContext.destination);
if(osc.type === "sine"){
osc.start(audioContext.currentTime);
}else if(osc.type === "sawtooth"){
osc.frequency.value = 50;
osc.start(audioContext.currentTime);
}else{
console.log("no condition met");
}
The following code is an example of a switch statement that checks the value of an
oscillator type and sets its frequency value based on its being a sine, sawtooth, or square wave. If
the oscillator is not one of these types, the default branch executes and sets
osc.frequency.value to 200.
//____________________________BEGIN Setup
var audioContext = new AudioContext();
var osc = audioContext.createOscillator();
osc.type = "sawtooth";
osc.connect(audioContext.destination);
//____________________________END Setup
//____________________________BEGIN Switch statement
switch(osc.type) {
case "sawtooth":
osc.frequency.value = 50;
osc.start(audioContext.currentTime)
break;
case "sine":
osc.frequency.value = 100;
osc.start(audioContext.currentTime);
break;
case "square":
osc.frequency.value = 150;
osc.start(audioContext.currentTime);
break;
default:
osc.frequency.value = 200;
osc.start(audioContext.currentTime);
}
//____________________________END Switch statement
Ternary Operator
If you are writing a conditional statement that contains a single comparison clause (it returns only
one of two conditions), then you can use a ternary operator. The ternary operator has three parts:
an expression and two executed statements. The first part is an expression that is tested for true or
false and is separated from the executed code by a question mark. If the expression evaluates to
true, the code to the left of the colon is run. If the expression evaluates to false, the code to the
right of the colon is run. The syntax of the ternary operator looks like this:
/*
expression ? if true run this code : if false run this code
*/
The following code is an example of the ternary operator in action. This code checks if the
oscillator type is set to “sawtooth”. If it is, the frequency is set to 50; otherwise, it is set to
500.
//______________________________BEGIN setup
var audioContext = new AudioContext();
var osc = audioContext.createOscillator();
osc.type = "sine";
osc.connect(audioContext.destination);
osc.start(audioContext.currentTime);
//_______________________________END setup
//_______________________________BEGIN Ternary example
osc.type === "sawtooth" ? osc.frequency.value = 50 : osc.frequency.
value = 500;
//_______________________________END Ternary example
Loops
Computers are very good at doing lots of simple tasks very fast. One of the tools available to
leverage this capability is loops. Loops allow you to repeat a task until a condition or set of
conditions are met. We will cover the following types of loops:
■ for
■ while
for Loops
The following code is an example of a for loop that counts to 16 and outputs each loop number
to the console. The text that follows explains the keywords and what each component of the for
loop does.
for(var i = 0; i <=16; i+=1){
console.log(i);
}
A for loop consists of the for keyword and opening and closing parentheses. Inside the
parentheses are three parts separated by semicolons. The first part is the initialization variable,
and in this case it is set to zero.
for(var i = 0; i <=16; i+=1){
console.log(i);
}
The next part is the conditional statement, which is used to determine a condition to check
upon each iteration of the loop. As long as this condition is true, the loop will iterate (run another
time). In the following example, the condition tells the for loop to continue iterating as long as
the value of the variable i is less than or equal to 16.
for(var i = 0; i <=16; i+=1){
console.log(i);
}
The next part is used to increment the initialization variable. On each loop, the variable i is
incremented by one and eventually reaches 17 and stops looping.
for(var i = 0; i <=16; i+=1){
console.log(i);
}
The last part of a for loop is the code block that is defined by the opening and closing curly
braces. Any code that is written in between these curly braces gets repeated for each loop
iteration.
for(var i = 0; i <=16; i+=1){
console.log(i); // code here gets repeated for each loop
}
When for loops are run, they are very fast. Below is a script that uses an additional helper
function to pause each iteration of a for loop. The loop modifies the frequency of a playing
oscillator on each iteration. The helper function pauses the loop (which is its only function), so
you can hear each change.
/*__________________________________BEGIN Helper function.
Ignore this code it is simply being used to pause the for loop */
function sleep(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() – start) > milliseconds){
break;
}
}
}
//__________________________________END Helper function
//__________________________________BEGIN Web Audio API setup
var audioContext = new AudioContext();
var osc = audioContext.createOscillator();
osc.type = "sawtooth";
osc.frequency.value = 30;
osc.connect(audioContext.destination);
osc.start(audioContext.currentTime);
If you want to modify each value in an existing array, you can do so by looping through the
array and modifying the value at each iteration. To do this, set the conditional statement
termination value to the length of the array. In the following code, this is done with
synths.length. You can then access the individual values of the array within the loop by
placing the iterator variable inside the brackets next to it.
var synths = [ 'synth-1', 'synth-2', 'synth-3', 'synth-4' ];
console.log(synths.length); //__This is 4
for (var i = 0; i < synths.length; i += 1) {
console.log(synths[i]);
}
The following code shows a modification of the previous code where each value in the array
has “0hz” appended to it.
var synths = ['synth-1', 'synth-2', 'synth-3', 'synth-4'];
for (var i = 0; i < synths.length; i += 1) {
synths[i] += "0hz";
}
console.log(synths); /*[ 'synth-10hz', 'synth-20hz', 'synth-30hz',
'synth-40hz']*/
while Loops
The while loop is useful when you are unsure of how many iterations will be needed to
complete a task. A simple example is a live podcast website that allows users to connect and
listen while a show is on the air. As a programmer you might not know how long the show will
last but you want to continuously check for new user connections for the duration of the show and
allow them to listen in. The pseudocode for this example might look something like this:
var onAir = true;
while(onAir){
// check for new visitors and connect them
}
The while loop consist of the while keyword, opening and closing parentheses, and
opening and closing curly braces. A conditional statement is placed in the parentheses, which
allows the loop to iterate as long as the condition remains true. When the condition becomes
false, the loop stops. The following example loops as long as the freq variable is greater than
zero. At each iteration, the freq variable decrements until it is zero and the loop terminates.
var freq = 7000;
while (freq > 0) {
console.log(freq);
freq -= 100;
}
In this chapter, you will learn about functions, various ways to work with functions, and variable
scope. Functions allow you to write code in a way that avoids repetition. They also allow you to
encapsulate your code and perform a specific task based on a set of inputs. Scope pertains to the
context in which variables are declared. JavaScript handles variables differently depending on
their scope, and you will learn how to use variables in functions when writing programs.
The following example shows how you can multiply the input by a value selected by the user,
which is coded in the form of a parameter called multiplier.
function effectsBox(input, multiplier) {
return input * multiplier;
}
console.log(effectsBox(120, 2)); // Output 240
Parts of a Function
To create a function, you start by typing the function keyword followed by a function name.
Immediately following the function name, you place opening and closing parentheses, and then
immediately after these you place opening and closing curly braces.
function add(){
// function body
}
You can give the function placeholders for input values called parameters, which you place
inside the parentheses and separate by commas.
function add(a, b){
The final part of a function is an optional return statement that outputs a value when the
function completes.
function add(a, b){
return a + b;
}
To run the function (also called invoking the function), you type the function name followed by
an opening parenthesis. If the function has parameters, you enter values for these, which in the
context of invoking the function are called arguments. You end the function with a closing
parenthesis.
add(2, 5); // 7
If you invoke the function with arguments not defined by the function, no error is returned and
the system ignores the additional arguments.
add(2, 5, 999); //__The third argument is ignored and output is 7
Function Expressions
As an alternative to using function declaration syntax, you can write your functions using
expression syntax, where you assign the function to a variable like this:
var add = function (a,b){
return a + b;
};
add(2,3); // 5
The function expression syntax emphasizes an important aspect of JavaScript functions: they
can be treated like data and passed around between variables. Here’s an example of the previous
code with a variable named container that stores the result of running the add function with
arguments 2 and 3:
var add = function (a, b) {
return a + b;
};
var container = add(2, 3);
console.log(container); // 5
playOsc("sine", 340);
//___________________________________BEGIN effectsBox
You can use the arguments object to create default values for function arguments. The
following code checks to see if an argument is undefined, and if it is, sets its value to “sawtooth.”
function playOsc(oscType) {
//_______Set default of oscType to sawtooth
if (arguments[0] === undefined) {
oscType = "sawtooth";
}
return oscType;
}
console.log(playOsc()); //___sawtooth
console.log(playOsc("sine")); //___sine
The arguments object can be combined with the length property and a conditional statement
to ensure that an error is given if any arguments are left empty. To create your own error
statement, you use the throw keyword. In the following code, the conditional statement checks to
see if the number of arguments is not two, and if the conditional evaluates to true, then an error is
given (or thrown) to indicate this result.
function playOsc(oscType,freq){
if(arguments.length !==2){
throw “Error! This function takes two arguments"
}
}
playOsc("sine"); //___Error! This function takes two arguments
You can add another check to ensure that the correct data types are being entered like this:
function playOsc(oscType, freq) {
if (arguments.length !== 2) {
throw "Error! This function takes two arguments";
}
//_____Check for correct argument data types
if (typeof oscType !== "string" || typeof freq !== "number") {
throw "Please enter the correct argument types";
}
}
playOsc(100, true); //___Please enter the correct argument types
You can also use the arguments object to limit an argument to a list of specific values. The
following function takes a single argument that is intended to be one of the four waveform types. If
the argument is not one of these four values, an error is thrown. When the function is invoked, the
code loops through an array of the four waveform types. If any of the waveform types matches the
argument value, a variable named waveformValid is set to the Boolean value true. Then a
conditional statement checks the value of waveformValid. If it is false, an error is thrown;
otherwise, the function runs to completion.
function playOsc(oscType){
var waveforms = ["sawtooth","sine","triange","square"];
var waveformValid = false;
for(var i =0; i < waveforms.length; i+=1){
if(arguments[0] === waveforms[i]){
waveformValid = true;
}
}
if(waveformValid === false){
throw "please enter sawtooth, sine, triangle or square
as an argument"
}
}
playOsc("fat beats");
/*___Error: Uncaught please enter sawtooth, sine, triangle or
square as an argument___*/
playOsc("square"); //___works
Function Scope
Scope is a concept that defines how one part of a program can access variables in another part of
a program. In the ECMAScript 5 version of JavaScript, there are only two forms of scopes: a
global scope and function scope (also called a local scope). This means that if you declare a
variable within a function, it is specific to that function and does not conflict with any other
variables that have the same name and are defined outside of that function. Functions have access
to their own variables and they also have access to any variables in a higher scope, which
includes the global scope.
In one of our previous examples, we created a function to play an oscillator. Notice that
although the audioContext variable is not included inside the playOsc function, it is still
accessible. This is because audioContext is defined in a higher scope: the global scope.
//____audioContext is global
var audioContext = new AudioContext();
//____ playOsc has access to it
function playOsc(oscType, freq){
var osc = audioContext.createOscillator();
osc.type = oscType;
osc.frequency.value = freq; //____freq is a parameter
osc.connect(audioContext.destination);
osc.start(audioContext.currentTime);
}
playOsc("sine", 330);//____Plays oscillator at 330hz
You can use function scope to protect variables defined in a function not only from variables
defined in a higher scope but from variables defined in other functions. In the following example,
there are two functions. One has data, and the other wants data. The data of the first function is
not accessible to the other function because it is hidden in a local scope.
function iHaveData() {
var data = "The data";
}
function iWantData() {
return data;
}
iWantData(); // data is not defined
If you declare two variables with the same name and one is globally scoped (or in a higher
scope) and the other is locally scoped within a function, the locally scoped variable is referenced
when the code in the function is running.
So, for example, in the following code, the multFreq function takes a single argument and
multiplies it by a value that is assigned to the multiplier variable. The globally scoped
multiplier is not referenced when multFreq() is running because the function has a
locally scoped variable with the same name.
var multiplier = 4; /*______This variable is not referenced by
multFreq*/
function multFreq(frequency) {
var multiplier = 2; //_____Because this one has the same name
return frequency * multiplier;
}
console.log(multFreq(200)); // 400
console.log(multiplier); // 4
If the locally scoped multiplier variable declaration inside the previous function is
removed, then during function execution, the code will look outside the function for a variable
with the referenced name.
var multiplier = 4;
function multFreq(frequency) {
/*__There is no local multiplier variable so it finds one in the
scope above it__*/
return frequency * multiplier;
}
console.log(multFreq(200)); // 800
Variable Hoisting
Whether you declare your variables globally or within a function, you should always declare
them at the top of the current scope. The reason for this is a phenomenon called hoisting. To
understand hoisting, you must first understand that variable declaration and initialization are two
different things. In the following code, a variable is declared using the var statement and then it
is initialized on the next line.
var myData; //_______________________________variable declared
myData = "important data goes here";//__variable initialized
//The following variable is declared and initialized in one line
var playOsc = false;
When you declare a variable, the JavaScript interpreter immediately (behind the scenes)
decouples the declaration from the initialization and moves the variable declaration to the top of
the current scope. The following example demonstrates this. The code on the left shows a function
named run that contains a variable named test, which is declared after it is initialized. When
this function is invoked, JavaScript changes the order and places the declaration at the top of the
current scope, in effect making the function look identical to the code on the right. This is why the
globally scoped test variable is not overwritten when the function is invoked, even though it
appears at first glance that it should be, because the local test variable is not yet declared.
Because of hoisting, it is considered best practice to declare your variables at the top of the
current scope, which is where they will be declared anyway.
How Hoisting Affects Functions
In addition to hoisting affecting variables, it also affects functions. And hoisting works differently
based on whether the function is written with declaration or expression syntax.
Consider the following function declaration. In this code, the function is invoked before it is
declared, yet it still works! This is because behind the scenes, the declaration is hoisted to the top
of the scope, which allows you to execute the function even though it is not yet declared.
multFreq(200, 2); /*__This still works even though it is invoked
before it is declared!_*/
function multFreq(input, val) {
return input * val;
}
The following example of the same function written using expression syntax, however, throws
an error. This happens because function expressions are treated like variables, with the
declaration being hoisted to the top. Remember that the initialization of the variable still happens
where the variable is initialized in the code. In this case, the function is run before the
initialization that defines the function occurs. The lesson here is that when you use function
expressions, you must declare functions before you invoke them. This is good practice with all
your functions as it makes your code less confusing and more readable.
multFreq(200, 2); //___ error! “multFreq is not a function”.
var multFreq = function(input, val) {
return input * val
}
Anonymous Functions
Anonymous functions are functions that do not have a name. Technically, the function in the
following code is an anonymous function because the variable it is assigned to is not the function
name. It is the container name for an anonymous function.
var multFreq = function(input, val) {
return input * val;
}
Note, however, that to invoke the function, you use the variable name that it is assigned to.
multFreq(100,2); // 200
The first thing to notice is that the function is wrapped in parentheses. This is optional, but is
considered best practice because it helps differentiate the construct syntactically from non-IIFE
functions.
(function run() {
return "data";
}());
The next thing to notice is the parentheses toward the end of the function before the closing,
encapsulating parenthesis. This syntax is what invokes the function.
(function run() {
return "data";
}());
To add parameters and arguments, you put parameters in the first set of parentheses and
arugments in the second set of parentheses.
(function add(a, b) { //_____ parameters
return a + b;
}(2, 3)); //___________________arguments
Closures
One of the most difficult aspects of the JavaScript language for new programmers to grasp is
closures. Understanding closures will ultimately allow you to write cleaner code while giving
you a powerful tool to solve a host of problems you will inevitably run into. Understanding the
concept of closure can be a bit difficult at first. But in the long term, the benefits are worth the
time investment.
What Is a Closure?
A closure is an inner function that has access to the scope of its outer environment even after that
outer environment has returned. To understand what this means, you must first solidify your
understanding of scope. The following example demonstrates how a function has access to its
local scope, the global scope, and its local arguments.
var globalVariable = "global variable";
function doSomething(argInput) {
var localVariable = "local variable";
console.log(argInput);
console.log(globalVariable);
console.log(localVariable);
}
If a function is defined inside another function, it too has access to the data of the harboring
function, as well as its own locally scoped variables. In the following example, testScope()
is a harboring function for innerFunction().
var globalVariable = "global variable";
function testScope(argInput) {
var testScopeLocalVariable = "local variable from testScope";
//____The inner function has access to everything outside of it
function innerFunction() {
var localVariable = "local variable from innerFunction";
console.log(argInput);
console.log(globalVariable);
console.log(testScopeLocalVariable);
console.log(localVariable);
}
innerFunction();
}
testScope("argument input");
As we mentioned, a closure is an inner function that has access to the scope of its outer
environment even after that outer environment has returned. The previous examples
demonstrated scope access. The following example demonstrates what it means for a function to
have scope access even after the outer environment has returned. The outer environment can be
either the global environment or another function. The following code includes the effectsBox
function that contains a single variable named component. The effectsBox function returns
a function that returns the value of component. When the initial effectsBox function is
invoked, it returns a function declaration named openEffectsBox to the outer scope (in this
case the global scope). This openEffectsBox function declaration is then assigned to a
variable called getComponent, which is then invoked and returns the string “Pulled out
component.”
The important thing to realize here is that a closure (the inner function) can return data (such as
the component variable) from its containing environment [in this case effectsBox()] even
after that outer environment [effectsBox()] has returned.
function effectsBox() {
var component = "Pulled out component";
return function openEffectsBox() {
return component;
};
}
var getComponent = effectsBox(); /*___stores "openEffectsBox"
function in a variable.*/
console.log(getComponent()); // "Pulled out component"
The previous example can be modified to demonstrate how state can be modified and
retained using the closure. In this code, there is an additional counter variable that increments
each time the inner openEffectsBox function is invoked. Since closures allow access to the
scope of a containing function even after that containing function has returned, the returned
function can continue to increment the counter variable and have access to its state.
function effectsBox() {
var counter = 0;
var component = "Pulled out component";
return function openEffectsBox() {
return component + " " + (counter += 1);
};
}
var getComponent = effectsBox(); //___stores "openEffectsBox"
function in a variable.
function playOsc(type) {
return function(freq) {
var osc = audioContext.createOscillator();
osc.type = type;
osc.frequency.value = freq;
osc.connect(audioContext.destination);
osc.start(audioContext.currentTime);
};
}
var sinewave = playOsc("sine");
sinewave(140); //____________Plays sine wave at 140 hz
Closure is an advanced concept that can be used to protect a portion of a program from the
global scope, retain state, and organize your code. Its specific use cases will gradually become
more apparent as your skill as a programmer develops. For now, it is important to grasp what a
closure is.
Callback Functions
A callback is a function that is used as an argument to another function. The following
example demonstrates addition of two numbers using a callback.
function doMath(callback) {
return callback();
}
function addTwoNumbers() {
return 2 + 2;
}
doMath(addTwoNumbers); // 4
When working with callbacks, you will often see function invocations where the callback
declaration is placed directly in a function argument.
function doStuff(callback) {
return callback();
}
The following function is an example of using a callback to make a function more flexible. The
calculateFrequencies function is designed to take three arguments. The first two are
numbers and the third is a callback that manipulates the other arguments. If the user does not use a
callback, then the function defaults to multiplying the two arguments together.
function calculateFrequencies(a, b, callback) {
if (callback === undefined) {
return a * b;
} else {
return callback(a, b);
}
}
function diff(a, b) {
return Math.abs(a - b);
}
The previous example demonstrates how passing a callback to a function provides the action
taken by the callback, whereas passing nonfunction values provides data input.
Array
Description
Method
Compares each element in an array to a conditional statement and returns a new
filter()
array of elements that meet the condition
Calls a function on each element in an array and returns a new array with the
map()
mapped value of each element in the input array
filter()
The filter method compares each element in an array to a conditional statement and returns a
new array of only those elements that meet the filter condition. The following example uses
filter() to loop through an array of frequency values to create a new array of values greater
than or equal to 1000.
var freq1 = 1200,
freq2 = 570,
freq3 = 100,
freq4 = 1500;
map()
The map function calls a function on each element in an array and returns a new array that
contains the mapped data for each element in the input array.
The following example uses map() to add 100 to each value in an array and return a new
array named newFreqs.
var freqs = [100, 200, 300];
var newFreqs = freqs.map(function (val) {
return val + 100;
});
The callback functions of both map() and filter() take three arguments. In order of their
position, these are value, index, and array. The value argument is the array value at the
current index, the index argument is the current index value, and the array argument is the
array that the callback is being applied to. In the following example, a map method is applied to
an array and all three arguments are logged to the console.
var freqs = [100, 200, 300];
var newFreqs = freqs.map(function(val, index, arr) {
var message = "current value: " + val + " current index index: "
+ index + " array: " + arr;
console.log(message);
return val;
});
/*___This logs the following to the console
current value: 100 current index: 0 array: 100,200,300
current value: 200 current index: 1 array: 100,200,300
current value: 300 current index: 2 array: 100,200,300
*/
Recursion
Recursion is an advanced programming topic, and it will only be explored briefly in this chapter.
A recursive function is a function that calls itself. The following is an example of a recursive
function.
function x(){
return x()
}
If you run the previous code, it will crash your browser. This is because, when a recursive
function runs indefinitely, it eventually uses up the resources of your code interpreter (in this case
the web browser) and creates an error. To use recursion effectively, you need to set a condition to
terminate the recursion. This condition is called the base case.
The following example is a recursive function named loopFromTo that contains a working
base case. loopFromTo takes two arguments, and both are numbers. In the function body, a
conditional is used to check if the argument named start is less than the argument named end.
As long as this condition is true, loopFromTo calls itself and on each iteration increments the
start argument by one. This continues until the recursion terminates when start ceases to be
less than end and the conditional statement evaluates to false.
function loopFromTo(start, end) {
console.log(start);
if (start < end) {
return loopFromTo(start += 1, end)
}
}
Recursive functions can be used in place of looping constructs and are an invaluable tool in
many complex algorithms. If recursion seems confusing don’t worry, you can program perfectly
good applications while you become familiar with it.
Summary
In this chapter, you learned how to create and use functions. In the next chapter, you will expand
your understanding of JavaScript to include a concept called object-orientated programming.
6 Objects
So far, we discussed five of JavaScript’s six data types. These are string, number, Boolean,
undefined, and null. These are called primitive data types. Anything that is not a primitive data
type is of the object data type. In the previous chapter, you learned about functions, which are of
the object data type. In this chapter, you will learn how to program using object literals, which
are also of the object data type.
A key is similar to a variable, and a value is similar to the data assigned to a variable. The key
and value of an object is called a property for nonfunctions assigned to a key, or a method for
functions assigned to a key.
var obj = {
key: "value", //___This is a property
doSomething: function(){ //___This is a method
}
};
Conceptually, object literals are used to model real-world elements in your code. So, for
example, the following object is used to model a music album.
//_________________This is an object that contains album data
var album = {
name:"Thriller Funk",
artist:"James Jackson",
format:"wave",
sampleRate:44100
}
To access data from an object, you can use dot notation, which looks like this:
album.name; // Thriller Funk
album.artist; // James Jackson
album.format; // wave
album.sampleRate; // 44100
If you use a bracket notation, you must type the key in the form of a string.
album["sampleRate"]; //__The key is a string
You can use methods to modify or retrieve data from an object. Here is an example of an
object that contains a method that returns the name and artist information of an object named song.
var song = {
name: "Funky Shuffle",
artist: "James Jackson",
format: "wave",
sampleRate: 44100,
//_____________________________BEGIN Method
nameAndArtist: function() {
return "Name: " + song.name + " | " + "Artist: " + song.artist
}
//_____________________________END Method
}
You can invoke methods with dot notation and trailing parentheses.
//__________________________________BEGIN method invocation
song.nameAndArtist(); // Name: Funky Shuffle| Artist: James Jackson
//__________________________________END method invocation
The structure of a for in loop consists of the for keyword followed by a variable that
represents the value of each property. In the previous example, this variable was named prop.
The variable name is followed by the in keyword and the name of the object you want to loop
through.
Often you will want to modify the properties of an object you are looping through while not
modifying any of its methods. One way you can do this is by using a conditional statement and the
typeof operator to act only on property values that are not functions. This usage is shown in the
following code:
var song = {
name: "Funky Shuffle",
artist: "James Jackson",
format: "wave",
sampleRate: 44100,
nameAndArtist: function() {
return "Name: " + song.name + " | " + "Artist: " + song.artist;
}
};
for (var prop in song) {
if (typeof song[prop] !== "function") {
console.log(song[prop]); //___Omits methods
}
}
When to Use Objects Rather Than Arrays
You have probably noticed that objects and arrays are similar because they allow you to organize
collections of data. If you are curious about when to use an array rather than an object, the rule of
thumb is that if the order of the data matters, you should always use an array. The reason for this
is that there is nothing in the JavaScript specification that guarantees the order in which key-value
pairs of an object are returned in a loop.
Cloning Objects
If you want to create an object that has access to another object’s properties and methods, while
being extensible, you can use the Object.create() function. The following example shows
an object being cloned using this method.
var effects = {
reverbs: {
hall: "Hall reverb being used",
plate: "Plate reverb being used",
smallRoom: "Small room reverb being used"
},
guitar: {
flange: "Flange being used",
wahWah: "Wah wah being used"
}
};
var updatedEffects = Object.create(effects);
console.log(updatedEffects.reverbs); //returns reverb object
console.log(updatedEffects.guitar); // returns guitar object
You can then extend the newly created object with properties and methods.
updatedEffects.filters = {
lowPass: "Lowpass filter being used",
highPass: "Highpass filter being used"
};
console.log(updatedEffects.filters); // returns filter object
console.log(effects.filters); // undefined
Prototypal Inheritance
It is important to understand that Object.create() does not literally copy the properties and
methods to a new object but provides a reference to the properties and methods contained in the
parent object(s). This hierarchy of references between objects is called prototypal inheritance.
The following code shows this by cloning multiple objects and including comments of the
hierarchy of property accessibility.
var synth = {
name: "Moog",
polyphony: 32
};
function getName() {
return this.name;
}
var getNameOfSong = getName.bind(song); /*assign bound function to
a variable*/
//_______Then invoke it!
console.log(getNameOfSong()); // Funky Shuffle
If you want to specify arguments in a function created with bind, you can do this in one of
two ways. The first is to specify the arguments in the newly created function. In the following
example, a function named descriptor is invoked on an object named blastSound. An
argument is then passed to the describeBlastSound function.
var blastSound = {
name: "Blast"
};
function descriptor(message) {
return this.name + ": " + message;
}
Alternatively, you can specify the arguments in the statement where you bind the function to the
object. You do this by first specifying the object to bind to, then specifying arguments you want to
use and separating them with commas, as in the following example:
var describeBlastSound = descriptor.bind(blastSound, "This is an
explosive sound");
console.log(describeBlastSound()); /*Blast: This is an explosive
sound*/
As you can see, even when a function has not been written as a method on a particular object,
you can still apply the function to that object. This also means that you can use a method of one
object and apply it to a completely different object. The following code uses a method named
getNameAndArtist of an object named song and applies it to an object named Album.
var album = {
name: "Funky Shuffle",
artist: "James Jackson",
format: "wave",
sampleRate: 44100
};
var song = {
name: "Analogue Heaven",
artist: "The Keep It Reels",
getNameAndArtist: function() {
If a function is invoked outside the context of an object, its this value points to one of two
values, depending on whether strict mode is used or not. If strict mode is used, its this value is
undefined. If strict mode is not used, its this value points to an invisible object called the
global object, which contains all the built-in properties and methods of the web browser. You can
view the value of this by using console.log(this) in the global scope without strict
mode.
Summary
In this chapter, you learned how to program with objects. In the next chapter, you will learn the
basics of the Web Audio API node graph and working with oscillators.
7 Node Graphs and Oscillators
In previous chapters, you learned the basics of working with JavaScript data types and how to
use the Web Audio API to generate basic tones. In this chapter, you will use your understanding of
JavaScript to get a better understanding of two core features of the Web Audio API: node graphs
and oscillators.
AudioContext() is a constructor that returns an object when you use the keyword new.
Constructors and the new keyword are explained in Chapter 12. For now, the important thing to
understand is that AudioContext() returns an object containing all of the methods and
properties that you use to access the Web Audio API.
Node Graphs
A node graph is a collection of nodes. A node in a node graph is an object that represents an
audio input source, such as an oscillator, or an object designed to manipulate an audio input
source, such as a filter. These nodes are connected together using a method named connect.
The following code is an example of an oscillator node connected to a filter node.
"use strict";
var audioContext = new AudioContext();
//_____________BEGIN create oscillator and filter
var filter = audioContext.createBiquadFilter();
var oscillator = audioContext.createOscillator();
//_____________END create oscillator and filter
//______________BEGIN connect oscillator to filter
oscillator.connect(filter);
//_____________END connect oscillator to filter
//_____________BEGIN connect filter to computer speakers
filter.connect(audioContext.destination);
//_____________END connect filter to computer speakers
oscillator.start(audioContext.currentTime);
//_____________END start oscillator playing
In the previous code, the oscillator object is created using the createOscillator method
of the audio context and stored in a variable named oscillator. You create the filter object in
a similar way by invoking the createBiquadFilter method of audioContext. The
oscillator is connected to the filter using connect(). The filter is connected to a
property named destination. The destination represents the output of your computer’s
audio system. To start the oscillator playing, you use a method of the oscillator object
named start. The start method takes one argument that determines the time the oscillator
starts playing. The value of audioContext.currentTime is the current time in seconds
within the Web Audio API, starting when AudioContext was invoked. (The topic of time is
discussed in Chapter 20.)
Oscillators
Oscillators, like all Web Audio API nodes, have their own custom properties and methods. The
following methods and properties are discussed in this chapter.
Method Description
start Starts oscillator playing
stop Stops oscillator playing
Property Description
oscillator.onended = function() {
oscillator.start(audioContext.currentTime); // fails!
};
The following code recreates an oscillator and starts it playing 1 second after the previous
oscillator stops.
var oscillator = audioContext.createOscillator();
oscillator.connect(audioContext.destination);
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + 3);
oscillator.onended = function() {
oscillator = audioContext.createOscillator();
oscillator.connect(audioContext.destination);
oscillator.start(audioContext.currentTime + 1); /*start in
one second*/
};
oscillator.onended = function() {
oscillator = audioContext.createOscillator();
oscillator.frequency.value = 130.81; // C3 note
oscillator.detune.value = 100; /*sets the note to one half step
higher to C#3*/
oscillator.connect(audioContext.destination);
oscillator.start(audioContext.currentTime + 0.5);
oscillator.stop(audioContext.currentTime + 2.5);
};
Summary
In this chapter, you learned the basics of node graphs and oscillators. In the next chapter, you will
learn the basics of HTML and CSS and create the interface for your first Web Audio API
applications.
8 Using HTML and CSS to Build User
Interfaces
In this chapter, you will learn the basics of HTML and CSS, giving you the necessary tools to
build user interfaces for your Web Audio API applications. You will do this by building a user
interface intended to trigger an oscillator that includes interactive controls to select frequency and
waveform type. In the next chapter, you will combine the interface with JavaScript code to build
your first working interactive application.
HTML
HTML stands for hypertext markup language and is the language used to create static websites.
In Chapter 1, you learned that HTML consists of elements, sometimes referred to as tags, that
make up the page of an HTML document. To be treated as an HTML document, a file must be
saved with .html appended to its name. A file extension is a group of characters placed after a
period in a file name that indicates the file’s format. In the case of a file named index.html,
the file extension is .html.
The following code is from the HTML template you created in Chapter 1. It consists of a
collection of elements required to make a document W3C compliant. W3C stands for World Wide
Web Consortium; this group is responsible for the development of web standards. Unlike
JavaScript, HTML does not return errors if your code is written incorrectly, so you need
additional tools to find HTML errors. You can test the compliance of an HTML document by
running your code through the HTML validation tool at the following URL:
https://validator.w3.org.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>app</title>
<script src="js/app.js"></script>
<link rel="stylesheet" href="css/app.css">
</head>
<!--_____________________________________________ BEGIN APP-->
<body>
</body>
<!--_____________________________________________ END APP-->
</html>
Inside the <body> element is where you write the bulk of your HTML code. In the following
example, the <p> and <h1> elements between the opening and closing body tags show how
HTML is used to display text. The <h1> element is a heading element and the <p> element is a
paragraph element. As the names imply, you use the heading element to create title headings and
the paragraph element to encapsulate text that represents a paragraph.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Template</title>
<script src="js/app.js"></script>
<link rel="stylesheet" href="css/app.css">
</head>
<!--____________________________________________BEGIN APP-->
<body>
<h1> Creating an Interface </h1>
<p>In this chapter we will go over HTML and CSS</p>
</body>
<!--____________________________________________END APP-->
</html>
When you write HTML and CSS, you must understand two primary concepts. First, HTML
web pages consist of a hierarchy of elements that form a nested tree-like structure. This is called
the HTML Document Object Model (or DOM for short). The following diagram reflects the node
tree of the previous example.
Second, most elements contain opening and closing tags that are used to encapsulate other
elements. The processes of encapsulating elements within other elements and treating the
containing elements as boxes is commonly referred to as the box model.
The following code emphasizes the box model by adding elements that contain other elements.
This includes a containing <div> that encapsulates a <form> element. The <form> element
then encapsulates <input>, <span>, and <p> (paragraph) elements.
<body>
<h1> Creating an Interface </h1>
<p>In this chapter we will go over HTML and CSS</p>
<div>
<form>
<input id ="on-off" type = "button" value="start">
<span>Click to start oscillator</span>
<p>Use slider to modify frequency</p>
<input type= "range">
</form>
</div>
</body>
The tree structure of the modified HTML is reflected in the following diagram.
The rendering of the code looks like the following figure.
HTML elements come in two categories: block-level and inline. The difference between the
two is that block-level elements display vertically and inline elements display horizontally. The
<div> and <span> elements are two elements that reflect these characteristics. <div> is a
block-level element and <span> is an inline element. These are both considered generic
container elements. This means that they convey no special meaning but are useful to help lend
structure to your page when no other elements are appropriate. The following code demonstrates
how these elements are interpreted when they are rendered in the browser. The <hr> element is
used solely to create a visual demarcation (horizontal line) between the two examples.
<body>
<span>
This text is inside a span
</span>
<span>
This text is inside a span
</span>
<hr>
<div>
This text is inside a div
</div>
<div>
This text is inside a div
</div>
</body>
The type attribute comes with a built-in list of possible settings, some of which are shown in
the following code. The value attribute gives the <input> element a default setting, as shown
in the following demonstration code (code that is not used in your final application):
<body>
<form>
<p>Input element type set to "button"</p>
<input type = "button" value="start">
<hr>
<p>Input element type set to "range"</p>
<input type= "range">
<hr>
<p>Input element set to a "number"</p>
<input type = "number" value="44.100">
<hr>
<p>Input element set to a "text"</p>
<input type= "text" value ="sine">
</form>
</body>
CSS
CSS stands for cascading style sheets and is the technology used to style web pages and web
applications. Like HTML, CSS does not throw errors when written improperly. To check for
errors, you can use the W3C CSS validator tool at this URL: https://jigsaw.w3.org/css-validator/.
CSS files use the .css file extension. To use CSS with an HTML file, you must first create a
CSS document and then connect it to your HTML document using the <link> element in the
<head>. The following example illustrates this usage, which is applied for the remainder of this
chapter.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>CSS and HTML</title>
<link rel="stylesheet" href="css/app.css">
</head>
<body>
<h1> Creating an Interface </h1>
<p>In this chapter we will go over HTML and CSS</p>
<div>
<form>
<input id ="on-off" type = "button" value="start">
<span>Click to start oscillator</span>
<p>Use slider to modify frequency</p>
<input type= "range">
</form>
</div>
</body>
<html>
To ensure that your CSS document is being read properly, open your HTML document in
Chrome and open the developer tools. If you made an error, the console will indicate this in red.
After the CSS file is linked you can begin to apply CSS styling to the HTML elements. For
example, if you want to change the background color of the page, in your CSS file you select the
body element and set the background-color property to a color value. This is shown in the
following example where the background color is changed to orange. As an alternative to using
the name of the color, you can set the color using a hex color code value such as #ffa500 or a
red–green–blue value such as rgb(255,165,0).
body{
background-color:orange;
}
The procedure for applying CSS to an element is as follows:
1. Select the element you want to affect and type its name in your CSS file. In the previous
example, this was body.
2. Type an opening and closing curly brace. These two braces are commonly referred to as a
code block. Inside the code block you place properties and set values following a colon. In
the previous example, the property was background-color and its value was orange.
Each property value setting ends with a semicolon.
The CSS specification includes many properties. A full list of properties is available at this URL:
https://developer.mozilla.org/en-US/docs/Web/CSS/Reference.
Comments
Just like HTML or JavaScript, you can add comments to your CSS file using the following syntax:
/* This is a CSS comment */
Element Selectors
When you select elements directly, all instances of the element are selected and the same CSS
styling is applied to them. For example, in the following demonstration code, every <div> on the
page is selected and given a background color of blue.
div{
background-color:blue;
}
Grouping Selectors
If you want to apply the same styles to multiple selectors you can do so in one line of code by
grouping selectors. You do this by separating each element with a comma. The following
demonstration code selects the <p>, <li>, and <h1> elements and applies the same font color to
each one.
p,li,h1{
color:green; //Changes font color of all three elements to green
}
Descendent Selectors
If you want to access an element only if it is nested inside of a particular element, you can do this
with descendent selectors. The CSS syntax for this type of selector is expressed by typing the
parent element, a space, and then the element you want to select. In the following code, a
descendant selector is used to select all <li> elements that are nested in any <div> element. In
the following demonstration code, the font color of each <li> element is set to blue.
div li{
color:blue;
}
It is important to realize that descendant selectors select all the descendent elements no matter
how nested they are. If the previous CSS example were applied to the following HTML code, it
would change the font color of all of the <li> elements to blue even though they are nested in a
<ul> element.
<div>
<ul>
<li>Item-1</li>
<li>Item-2</li>
<li>Item-3</li>
</ul>
</div>
Child Selectors
Child selectors are similar to descendent selectors with the difference that the selected element
can only be one level deep relative to the parent. A child selector is made using the “>” symbol
with the parent element on its left side and the child element on its right. The following
demonstration code will select all <li> elements that are children of <ul> elements.
ul > li{
/* do something */
}
class and id
Often when selecting elements, you do not want to select every element of a particular type.
Rather, you might want to select either individual instances of elements or groups of elements.
You can single out an individual element for styling by using an identifier called id. Conversely,
you can designate a collection of elements as a group by using an identifier called class.
To single out an element using an id, you must first define id as an attribute of an HTML
element. The syntax looks like the following:
<div id = "controls">
<!—- content -->
</div>
In your CSS you can then select this individual element by preceding its id with a hashtag
character.
#controls{
/* properties and values go here */
}
Keep in mind that id names are intended to be used only once in your HTML and are applied
to a single element!
<div>
<h2>Waveform</h2>
<ul id="oscillator-list">
<li id="sawtooth">sawtooth</li>
<li id="sine"> sine</li>
<li id="triangle">triangle</li>
<li id="square">square</li>
</ul>
</div>
</body>
In the following CSS, each <li> element is selected via its id and given a background color.
#sawtooth{
background-color: #336E91;
}
#sine{
background-color: #783d47;
}
#triangle{
background-color: #3b3040;
}
#square{
background-color: #b85635;
}
Now that you know how to single out an element using id selectors, it is time to learn how to
group elements using class selectors.
In your HTML document, assign the two <div> elements the class osc-controls. Then
encapsulate the first <h1> and <p> inside a new <div>, and place another <div> element at
the bottom of the page that contains a paragraph element with the phrase “JavaScript for Sound
Artists Demo” inside of it.
You should have a total of four <div> elements in this code, which looks like this:
<body>
<div>
<h1> Creating an Interface </h1>
<p>In this chapter we will go over HTML and CSS</p>
</div>
<div class="osc-controls">
<form>
<input id ="on-off" type = "button" value="start">
<span>Click to start oscillator</span>
<p>Use slider to modify frequency</p>
<input type= "range">
</form>
</div>
<div class="osc-controls">
<h2>Waveform</h2>
<ul id="oscillator-list">
<li id="sawtooth">sawtooth</li>
<li id="sine"> sine</li>
<li id="triangle">triangle</li>
<li id="square">square</li>
</ul>
</div>
<div>
<p>JavaScript for Sound Artists Demo</p>
</div>
</body>
To select the osc-controls class from CSS, you must preface the class name with a dot.
.osc-controls{ /* Notice the dot selector */
/* set property values */
}
In the following code, the osc-controls class is given a border. Only the middle two
<div> elements respond to these changes because the first and third <div> on the page do not
have the class osc-controls assigned to them.
.osc-controls {
border-style:solid;
border-color: #BC6527;
border-width: 2px;
border-radius:10px;
}
You might notice that the page looks a little awkward because the start button is now pushed up
against the left edge of its container. Also, the two <divs> with borders are stacked directly on
top of one another with no space between them. You could make this look a bit cleaner by
creating some space between these elements. To do this, you should have an understanding of the
following three properties: margin, border, and padding.
To create a bit of space between the text and a <div> element border, include the following
code in your CSS file:
div{
padding:20px;
}
If you want to apply padding or a margin to only specific sides of an element, you can do so by
using the following properties:
margin-top
margin-right
margin-bottom
margin-left
padding-top
padding-right
padding-bottom
padding-left
The two outlines around the middle <div> elements could use some space between them. The
following code creates this space by using the bottom-margin property with a value of
20px.
.osc-controls {
border-style:solid;
border-color: #BC6527;
border-width: 2px;
border-radius:10px;
margin-bottom:20px;
}
The default font type for Chrome is Times New Roman. You can change the font type if you
like. The following code changes the font type to Arial.
body{
background-color:orange;
font-family: "Arial";
}
The font color of the text describing the waveform types is black, which is difficult to read
because the background color of each <li> is dark. The following code changes the text color
property to white.
#oscillator-list li{
list-style-type: none;
color:white;
}
Centering Block-Level Elements
If you want to center a block-level element, you can do so by setting its width to a value smaller
than its containing element and applying a margin property with a value of 0 auto. This sets
the left and right margin values to automatically extend to the boundaries of the container,
centering the element. In the following code, a <div> with a class of application contains
all the HTML within the body element. Its CSS is set to a fixed width and centered by specifying
margin: 0 auto.
<body>
<div class="application">
<div>
<h1> Creating an Interface </h1>
<p>In this chapter we will go over HTML and CSS</p>
</div>
<div class="osc-controls">
<form>
<input id ="on-off" type = "button" value="start">
<span>Click to start oscillator</span>
<p>Use slider to modify frequency</p>
<input type= "range">
</form>
</div>
<div class="osc-controls">
<h2>Waveform</h2>
<ul id="oscillator-list">
<li id="sawtooth">sawtooth</li>
<li id="sine"> sine</li>
<li id="triangle">triangle</li>
<li id="square">square</li>
</ul>
</div>
<div>
<p>JavaScript for Sound Artists Demo</p>
</div>
</div>
</body>
As a final step, remove the first <p> element and replace the text of the <h1> element with the
title Oscillator Generator.
<div class="application">
<div>
<h1> Oscillator Generator </h1>
</div>
<div class="osc-controls">
<form>
<input id ="on-off" type = "button" value="start">
<span>Click to start oscillator</span>
<p>Use slider to modify frequency</p>
<input type= "range">
</form>
</div>
<div class="osc-controls">
<h2>Waveform</h2>
<ul id="oscillator-list">
<li id="sawtooth">sawtooth</li>
<li id="sine"> sine</li>
<li id="triangle">triangle</li>
<li id="square">square</li>
</ul>
</div>
<div>
<p>JavaScript for Sound Artists Demo</p>
</div>
</div>
Summary
In this chapter, you created the user interface for a small application. In the next chapter, you will
add JavaScript code to make the application fully functional.
9 DOM Programming
with JavaScript
This chapter shows you how to add JavaScript to CSS and HTML. By the end of the chapter, you
will have created a fully functioning application with interactive controls.
HTML
<body>
<input id="play-button" type="button" value="PLAY">
</body>
JavaScript
window.onload = function() {
var playButton = document.getElementById("play-button");
playButton.addEventListener("click", function() {
alert("You clicked the play button");
});
};
The next line of code applies the eventListener method to the playButton.
playButton.addEventListener("click", function() {
alert("You clicked the play button");
});
In the app.js file, make sure you have strict mode enabled. All JavaScript code is written
below the use strict string.
"use strict";
Although this code starts the oscillator playing, it does not stop it from playing. The following
changes implement the stop feature by adding a conditional statement to addEventListener
to check whether a variable named osc is set to false. If osc is false, an oscillator is created
and assigned to it. This makes the Boolean value of the osc variable true, and the start method
is invoked, allowing the oscillator to play. If the user clicks the Start button again, the conditional
statement sees that the osc variable has the Boolean value true and runs the code in the else
branch. This stops the oscillator from playing and resets osc to false.
"use strict";
var audioContext = new AudioContext();
window.onload = function() {
var onOff = document.getElementById("on-off");
/*_________________________________________BEGIN set initial
osc state to false*/
osc = audioContext.createOscillator();
osc.type = "sawtooth";
osc.frequency.value = 300;
osc.connect(audioContext.destination);
osc.start(audioContext.currentTime);
osc.stop(audioContext.currentTime);
osc = false;
}
/*_____________________________________END Conditional
statement to check if osc is TRUE or FALSE*/
});
};
onOff.addEventListener("click", function() {
/*_____________________________________BEGIN Conditional
statement to check if osc is TRUE or FALSE*/
osc.stop(audioContext.currentTime);
osc = false;
onOff.value = "start";
span.innerHTML = "Click to start oscillator";
}
/*_____________________________________END Conditional
statement to check if osc is TRUE or FALSE*/
});
};
This code introduces a new DOM method called getElementsByTagName as well as two
new DOM properties: innerHTML and value. The getElementsByTagName method
allows you to select a collection of elements on the page by tag name. You can then specify an
individual element using array-style index selectors. The index selection represents the order of
the element on the page, with the first element starting at 0. To select the first span element on the
page, you specify getElementsByTagName(“span”)[0]. If there are several span
elements and you want to select the third one from the top of the page, you specify the index as 2,
and the selector looks like this:
document.getElementsByTagName("span")[2]
It is important to understand that DOM elements are not arrays, even though the notation used
to select them is similar to that used for arrays. DOM elements are referred to as nodes.
console.log(freqSliderVal);
osc.stop(audioContext.currentTime);
osc = false;
onOff.value = "start";
span.innerHTML = "Click to start oscillator";
}
/*_____________________________________END Conditional
statement to check if osc is TRUE or FALSE*/
});
};
setInterval(function() {
if (!osc) {
} else {
freqSliderVal = document.getElementsByTagName("input")[1].value;
osc.frequency.value = freqSliderVal;
console.log("Oscillator is playing. Frequency value is " +
freqSliderVal);
}
}, 50);
onOff.addEventListener("click", function() {
/*_____________________________________BEGIN Conditional
statement to check if osc is TRUE or FALSE*/
osc.stop(audioContext.currentTime);
osc = false;
onOff.value = "start";
span.innerHTML = "Click to start oscillator";
}
/*_____________________________________END Conditional
statement to check if osc is TRUE or FALSE*/
});
};
To do this, use the eventListener() method to capture the id of the element clicked by
the user. Because the id of each <li> is the name of a waveform, you must assign this id to the
osc.type property. You want users to be able to update the waveform type without having to
repeatedly restart the oscillator, and you can do this similarly to the frequency slider changes in
the previous example.
In the following code, you create a variable named selectedWaveform to give the
oscillator a default waveform type and to store any selected waveform changes.
var selectedWaveform = "sawtooth";
Next, create a variable named waveformTypes and assign it the result of calling
getElementsByTagName(). The waveformTypes value is used to select one of the four
<li> elements on the page.
var waveformTypes = document.getElementsByTagName('li');
Next, create a function named select that is used as a callback for a series of event listeners
used to select the id of the <li> clicked by the user.
function select() {
selectedWaveform = document.getElementById(this.id).id;
console.log(selectedWaveform); // Outputs id
}
Next, a for loop is used to examine each <li> node and assign an event listener to each one.
Each event listener is set to respond to a click event that invokes the callback function select.
When the select function runs, it captures the id of the <li> element clicked by the user. This
id is then stored in the selectedWaveform variable.
for (var i = 0; i < waveformTypes.length; i++) {
waveformTypes[i].addEventListener('click', select, false);
}
/*_________________________________________BEGIN callback to
select <li> by id and assign id name to selectWaveform*/
function select() {
selectedWaveform = document.getElementById(this.id).id;
console.log(selectedWaveform);
}
if (!osc) {
} else {
freqSliderVal = document.getElementsByTagName("input")[1].
value;
osc.frequency.value = freqSliderVal;
console.log("Oscillator is playing. Frequency value is " +
freqSliderVal);
osc.type = selectedWaveform;
}
}, 50);
onOff.addEventListener("click", function() {
if (!osc) {
osc = audioContext.createOscillator();
osc.type = selectedWaveform;
osc.frequency.value = freqSliderVal;
osc.connect(audioContext.destination);
osc.start(audioContext.currentTime);
onOff.value = "stop";
span.innerHTML = "Click to stop oscillator";
} else {
osc.stop(audioContext.currentTime);
osc = false;
onOff.value = "start";
span.innerHTML = "Click to start oscillator";
}
});
};
In the JavaScript file, add the following code to the select function.
function select() {
selectedWaveform = document.getElementById(this.id).id;
console.log(selectedWaveform);
selectedWaveformElement.classList.add("selected-waveform");
/*_____________________________________END add the selected-
waveform class to the selected element*/
}
Summary
In this chapter, you learned how JavaScript interacts with the DOM. In the next chapter, you will
learn the basics of a library named JQuery that makes DOM programming with JavaScript easier.
10 Simplifying DOM Programming with
JQuery
In the previous chapter, you learned how JavaScript interacts with the DOM. In this chapter, you
will learn how to simplify the process of adding interactive components to your application by
using a library called JQuery. The objective of this chapter is not to teach you the entire JQuery
API, but to give you the foundational knowledge to make JQuery a part of your programming
toolkit. You can find the JQuery API at this URL: https://api.jquery.com/.
What Is JQuery?
JQuery is a library written in JavaScript intended primarily for DOM manipulation. A library is a
collection of preassembled code pieces designed to make a particular group of tasks easier.
JQuery contains a large collection of methods and properties that can be used individually or
combined to help ease the complexity of JavaScript DOM programming.
JQuery Setup
You can set up JQuery in one of two ways. The first is to download the library and reference it
from an HTML file. The second is to reference it from a content delivery network (CDN for
short). A CDN is a service accessible through the World Wide Web where you can reference code
libraries and other files. The downside of using a CDN is that you will always need a working
Internet connection to access it.
</body>
<!--_____________________________________________ END APP-->
</html>
</body>
<!--_____________________________________________ END APP-->
</html>
In the previous chapter, your JavaScript code was encapsulated in the following function:
window.onload = function() {
// code goes here
};
This was done to ensure that the code loads after the browser renders the HTML document.
JQuery comes bundled with a function that does the same thing with slightly different syntax.
$(function(){
// code goes here
});
How to Use JQuery
The most fundamental use cases for JQuery require knowledge of two things. The first is how to
select an HTML element. The second is how to do something with that element.
The syntax for element selectors always begins with a dollar sign, followed by two
parentheses. You place the element wrapped in quotes inside the parentheses. JQuery selectors
borrow from CSS selector syntax. If you know how to select elements using CSS, you can quickly
learn to select elements in JQuery. The following are a few examples of CSS selectors and their
JQuery counterparts.
});
Using Methods
After you have selected an element, you can modify it in some way using JQuery’s built-in
methods. JQuery comes with a large collection of methods; however, in this chapter we are only
going to use the following methods:
The following is an example of using css() to modify the look of an element. css() can be
used either to change a single property or to set multiple properties using an object as an
argument. An example of both use cases is given in the following code:
HTML
<div>Play</div>
<div>Stop</div>
<div>Rewind</div>
<div>Fast Forward</div>
<div>Pause</div>
Method Chaining
If you want to apply multiple methods to an element, you can connect them in succession so that
they are invoked one after another. This is called method chaining.
In the following code, all of the div elements have their CSS display property set to none.
JQuery is used to select the div elements and set their CSS properties using css(). The
fadeIn method is then chained to each div, so that every div on the page fades in over the
course of 1 second (1000 milliseconds). The first argument of fadeIn() is the duration of the
animation in milliseconds. When fadeIn()completes, the fadeOut method is invoked, which
fades out all div elements over the course of 1 second.
HTML
<div>Play</div>
<div>Stop</div>
<div>Rewind</div>
<div>Fast Forward</div>
<div>Pause</div>
CSS
div{
display:none;
}
JQuery/JavaScript
$(function() {
$("div").css({
backgroundColor: "orange",
width: "100px",
borderStyle: "solid"
}).fadeIn(1000).fadeOut(1000); // example of method chaining
});
The following HTML code contains an input element with its type attribute set to
button. This is selected with JQuery and set to respond to click events via an event listener.
The method used for this is on(), which takes two arguments. The first argument is a string that
defines the event type and the second is a callback that is invoked when the event is fired.
HTML
<input type="button" value = "Play">
JQuery/JavaScript
$(function() {
$("input").on("click",function(){ //click event listener
alert("You clicked play");
});
});
HTML
<input type="button" value = "Play">
<input type="button" value = "Pause">
<input type="button" value = "Stop">
JQuery/JavaScript
$(function() {
$("input").on("click",function(){ /*assign event listener to all
input elements*/
console.log($(this).val()); /*use the this keyword to access the
element clicked and return its value property*/
});
});
Replace the app.js file with a new empty file with the same name.
In the old application, you used this function to encapsulate your code:
window.onload = function() {
}
In the new version of your code, make sure you replace window.onload with the
equivalent JQuery function. Also put "use strict" and the AudioContext instantiation at
the top of the file, as in the following example:
"use strict";
var audioContext = new AudioContext();
$(function(){
// all code will go here
});
Next, modify the first three variables of the JavaScript file to use JQuery selectors.
Without JQuery
With JQuery
This code uses $("#on-off") to select the oscillator start or stop button by id. It is
denoted by the hash selector. You then use $("span") to select the span that contains message
text. This selection is done by element and, because there is only one span element on the page,
you do not need to be more specific. Lastly, $("input").eq(1).val() is used to select the
range slider value of the second input element on the page, which is stored in a variable named
$freqSliderVal. This is done by making a general element selection for all input elements
and specifying the second one on the page with the eq(1) method. The eq() method enables
selection of elements by index with its argument being the index value. Once the correct input
element is selected, val() is used to get its value attribute.
The refactored JQuery code assigns a click event listener to all <li> elements. When the user
clicks an <li> element, its id is stored in a variable named selectedWaveform.
selectedWaveform is referenced in a higher scope and is used later to set the oscillator type.
The removeClass() method is used to remove the selected-wavefrom class from all
<li> elements. The last line of code uses $(this) to select the specific <li> the user clicked
and invokes addClass() to give it the class selected-waveform.
The remaining changes require you to modify the name of the onOff selector variable to
$onOff and replace the addEventListener with the on() method set to respond to click
events. Then rename the freqSliderVal to $freqSliderVal, replace the
span.innerHTML with $messageText.text(), and lastly replace onOff.value with
the JQuery equivalent of $onOff.val().
Summary
In this chapter, you learned the basics of using JQuery for DOM manipulation. You also
refactored the code in the previous chapter to contrast the difference between working with and
without JQuery. In the next chapter, you will learn how to import and play back audio files with
the Web Audio API.
11 Loading and Playing Audio Files
In this chapter, you will learn the basics of working with audio files. This includes how to load,
play, and run audio files through the node graph to take advantage of its built-in effects.
Prerequisites
To load and play back audio files, you must be running a web server. Chapter 1 gives you
instructions about how to integrate a web server with Sublime Text by installing a package called
Sublime Server. Your audio files are referenced from the directory the web server is pointing to,
which you can set up as follows:
1. Create a folder on your desktop with a new project template.
2. Start the Sublime Text web server by selecting Tools > SublimeServer > Start
SublimeServer.
3. Open the sidebar (if it isn’t already open) by selecting View > Side Bar > Show Side Bar.
4. Drag the template folder to the side bar panel.
5. Open a web browser and enter: http://localhost:8080 in the URL field.
6. Click the link to the template folder. An empty screen is displayed because the initial
template is empty.
For this exercise, you will need an audio file that is short and preferably of MP3 format (for
compatibility issues). The file referenced in the example code is snare.mp3. Create a
directory in your template folder and name it sounds, then copy your MP3 file there.
getSound.onload = function() {
audioContext.decodeAudioData(getSound.response, function(buffer) {
audioBuffer = buffer;
});
};
getSound.send();
function playback() {
var playSound = audioContext.createBufferSource();
playSound.buffer = audioBuffer;
playSound.connect(audioContext.destination);
playSound.start(audioContext.currentTime);
}
window.addEventListener("mousedown", playback);
To clearly understand the purpose of the first argument requires a brief explanation of a
command called a get request.
get Requests
When you type a URL into a web browser and “go” to a website, the web browser does not
actually go anywhere. What actually happens is the browser issues a command to the website
server that initiates a download of the HTML content and other files needed to view it. This
command is called a get request. The beauty of get requests is that you can use them outside the
context of typing a URL into a browser. In other words, you can write code to run get requests
behind the scenes. This is how XMLHttpRequest is used to pull audio files into your
application—and why the first argument of the open method is “get”.
The second argument to the open method is the path to the file you want to fetch. For this
example, an MP3 file named snare.mp3 is imported.
The next line of code begins with an onload function that is invoked after the data (the audio
file) has completed loading. Within the onload function, decoding of the audio data takes place
that makes it usable by the Web Audio API. You do this with a method called
decodeAudioData that takes two arguments. The first argument is a property called
response that represents the loaded (and undecoded) audio data.
getSound.onload = function() {
audioContext.decodeAudioData(getSound.response, function(buffer) {
audioBuffer = buffer;
});
};
The second argument to the onload function is a callback function that allows you to capture
the result of the decoded audio data and do something with it. To capture the decoded file, you
must pass it as an argument of the callback function. In this case, the name given for this decoded
information is buffer. To make buffer accessible to the rest of the program, you can assign it
to a global variable.
var audioContext = new AudioContext();
var audioBuffer;
var getSound = new XMLHttpRequest();
getSound.open("get", "snare.mp3", true);
getSound.responseType = "arraybuffer";
getSound.onload = function() {
audioContext.decodeAudioData(getSound.response, function(buffer) {
audioBuffer = buffer; // stored as global variable
});
};
The last line is the send method. This method initiates the XMLHttpRequest.
getSound.send();
Now that the audio file is loaded into a buffer, the playback function contains the required
code to connect it to the node graph and eventually play it back. The first line assigns a method
called createBufferSource to a variable. This method is used to create a buffer source
node that is used for audio buffers. In other words, it is like createOscillator, but instead
of being used to create oscillators, it is used to create a node that can play back the contents of an
audio buffer. To inject the audio buffer into the node graph, you need to assign it to a property of
the buffer source node named buffer.
function playback() {
You can now connect the buffer to the audioConext.destination and set the start time.
function playback() {
The last line of code is an event listener that lets you play back the file when the window is
clicked.
window.addEventListener("mousedown", playback);
If you click on the page, you should hear the audio file play.
Summary
Each time you want to import an audio file into your program, you must initiate
XMLHttpRequest with all the method and property settings shown in this chapter. You can
imagine that duplicating this code repeatedly for each file is unfeasible for a large-scale
application. By abstracting away this complexity, you can program a solution to this problem that
lets you import multiple audio files with only a few lines of code. In the next two chapters, you
will learn how to do this while learning about two new object creation methodologies: factories
and constructors.
12 Factories and Constructors
In the previous chapter, you learned how to import audio files. You also learned that loading
multiple files can require a tremendous amount of code duplication. Because repeating code is
something that should be avoided, it is a good idea to abstract your audio file loading program
into a library that imports all the required files with a minimal amount of code duplication. In this
chapter, you will learn two new object creation patterns to help you do this. The first pattern,
called factory, is used to create your audio loader library. The second pattern, called
constructor, is introduced primarily because of its prevalence in the JavaScript world, making it
an important pattern to familiarize yourself with. Factories and constructors are almost identical.
The difference lies solely in minor implementation details and syntax. In other words, anything
you can do with one of these patterns you can do with the other. Your choice of which to use
comes down to personal choice.
In the next chapter, you will put what you learn here to work and build your audio file loading
library.
You can use factories to set properties and methods on the objects they return. In the following
example, the factory makeRecord is used to create objects that represent music albums. With
factories, property values are assigned to the returned object through function arguments. In the
following example, the object’s property values represent information about each record,
including title, artist, and year.
function makeRecord(title, artist, year) {
var record = {};
record.title = title;
record.artist = artist;
record.year = year;
return record;
}
var weAreHardcore = makeRecord("We Are Hardcore", "The Psycho
Electros", 2016);
If you want to create default values for properties, you can assign them like this:
function makeRecord(title, artist, year) {
var record = {};
record.title = title;
record.artist = artist;
record.year = year;
record.fullAlbum = true;
return record;
}
return record;
}
var weAreHardcore = makeRecord("We Are Hardcore",
"The Psycho Electros", 2016);
weAreHardcore.getAllProperties();
/*_____________________RESULT
title:We Are Hardcore
artist:The Psycho Electros
year:2016
leadSinger:Fred The Butcher
___________________________*/
Private Data
Sometimes you want to create data that is accessible to your objects but is either inaccessible to
the outside scope or cannot be changed. To do this, you can make data private by assigning it to a
variable inside the factory. In the following example, a variable named id stores some private
information.
function makeRecord(id) {
var id = id; // Private data
console.log(id + " is private data");
var record = {};
return record;
}
var myRecord = makeRecord("2323415432");
console.log(myRecord.id); /*undefined. This is a property of the
object, not the private data!*/
myRecord.getId(); // 1121210937
Conversely, methods that are used to modify private data are called setters. In the following
code, a setter is created that allows you to change the value of id while restricting the input to a
ten-digit string.
function makeRecord(id) {
var id = id;
var record = {};
record.getId = function() {
return id;
};
record.setId = function(newId) {
if (typeof newId === "string" && newId.length === 10) {
id = newId;
} else {
throw ("id must be a ten-digit string");
}
};
return record;
}
var myRecord = makeRecord("9876543210");
myRecord.getId(); // 9876543210
myRecord.setId("1000000001");
myRecord.getId(); // 1000000001
Programming with factories is a common pattern in JavaScript and one that you should be sure
to familiarize yourself with. Factories give you a simple syntax for abstracting complex code,
while offering you the privacy of function scope coupled with the flexibility of object extension.
As you can see, there are some differences between factories and constructors. The first is the
naming convention for functions. With constructors, it is considered good practice to name them
with a capitalized noun. This convention exists solely to help distinguish constructors from
nonconstructors and does not throw an error if it is not used. The lack of an explicitly created
object is the next difference. With constructors, instead of immediately creating an object in your
function declaration, begin by writing your properties using the this keyword. In a constructor,
this points to the object that is created from it. These properties are assigned values through the
constructor function arguments or, if you want to create default values, you can assign them
directly to the property.
function Record(title, artist, year) {
this.title = title;
this.artist = artist;
this.year = year;
this.fullAlbum = true; // default value
}
console.log(weAreHardcore.fullAlbum); // true
You invoke a constructor using the new keyword. This is the command that tells the interpreter
that you are using the function as a constructor. In response, the interpreter creates and returns an
object. In the previous example, the return value is assigned to the variable named
weAreHardcore.
Admittedly, this syntax is a bit odd looking. So, to clarify what is happening, let’s look at two
concepts interwoven with constructors: the prototype object and the prototype property.
Although you can attach your methods without using the prototype property, the drawback to
this approach is that every time you create a new object, all of the methods are initialized, and
this requires more memory. This might have been a concern in 1995 when JavaScript was
designed and computers were much slower, but the large amount of available memory in modern
computers makes this issue negligible. This is the reason factories are a viable alternative. The
syntax for adding methods without using the prototype object looks like this:
function Record(title, artist, year) {
this.title = title;
this.artist = artist;
this.year = year;
this.summary = function() {
return "Title:" + this.title + ". Artist:" + this.artist +
". Year:" + this.year;
};
}
You can use getters and setters, as you do with factories, to work with private data in
constructors. The following example contains a private variable named id and uses a getter to
retrieve it, as well as a setter that allows it to be changed to a ten-digit string. Note that the getter
and setter are not implemented on the prototype property, because if they were, the private data
would not be available to them.
function Record(id) {
var id = id;
this.getId = function() {
return id;
};
this.setId = function(newId) {
if (typeof newId === "string" & & newId.length === 10) {
id = newId;
} else {
throw ("id must be a ten digit string");
}
};
}
var myRecord = new Record("9876543210");
myRecord.getId(); // 9876543210
myRecord.setId("0123456789");
myRecord.getId(); //0123456789
Why Do Constructors Exist If You Can Do the
Same Thing with Factories?
At the time when JavaScript was developed in 1995, one of the most popular languages in the
world was Java. Out of a desire to appease Java developers and lure them into using JavaScript,
the language was designed to mirror Java’s syntax. Part of this effort included adding constructors
to the language that were designed to look like Java classes. This happened irrespective of the
fact that behind the scenes JavaScript is not a class-based language.
Summary
In this chapter, you learned how to create JavaScript pseudoclasses using factories and
constructors. In the next chapter, you will create a simplified audio file loader library using the
factory pattern.
13 Abstracting the File Loader
Now that you are familiar with factories and constructors from the previous chapter, you can
abstract the audio buffer loader you created in Chapter 11 into a library that loads multiple sound
files using less code. You do this using the factory pattern.
With this approach, a factory function takes an object as an argument. The object you input into
the factory contains a list of property names, each of which is assigned a directory of an audio file
in the form of a string. The beauty of this approach is its clarity and extensibility. The interface
shown in sound.snare.play() attempts to read, somewhat like English, from the list of
sound files to play. Even if you have never seen this code before, you can understand what it is
doing: selecting a sound named snare and playing it. Decoupling the object that contains many
audio files from the invoking function makes the code easier to read, as shown in the following
example:
var audioFiles = {
kick: "kick.mp3",
snare: "snare.mp3",
hihat: "hihat.mp3",
shaker: "shaker.mp3"
//______hundreds of audio files could be listed here.........
};
If the user of your abstraction decides they want to extend it to do new things, without having
to modify the source code in the original function, they have some flexibility. So for example, if
they wanted to extend the returned object to play multiple audio buffers, they could do this:
sound.playSnareAndShaker = function() {
sound.snare.play();
sound.shaker.play();
};
});
};
getSound.send();
soundObj.play = function(time) {
playSound = audioContext.createBufferSource();
playSound.buffer = soundObj.soundToPlay;
playSound.connect(audioContext.destination);
playSound.start(audioContext.currentTime + time ||
audioContext.currentTime);
};
soundObj.stop = function(time) {
playSound.stop(audioContext.currentTime + time || audioContext.
currentTime);
};
return soundObj;
}
function audioBatchLoader(obj) {
return obj;
kick: "sounds/kick.mp3",
snare: "sounds/snare.mp3",
hihat: "sounds/hihat.mp3",
shaker: "sounds/shaker.mp3"
});
window.addEventListener("mousedown", function() {
sound.snare.play();
});
Create a folder named sounds. This is the directory used to hold your audio files.
Walking through the Code
The function named audioFileLoader creates and returns an object named soundObj.
function audioFileLoader(){
var soundObj = {};
return soundObj;
};
You can now create the XMLHttpRequest object and set all the required properties and
methods. You can also implement the decodeAudioData method to make the buffer usable by
the Web Audio API. These lines of code should already be familiar to you because they are the
same buffer loading and decoding tools you learned about in Chapter 11, with one small
difference. In Chapter 11 the decoded buffer was assigned to a variable named audioBuffer.
In this implementation, the decoded buffer is assigned to a property of soundObj named
soundToPlay.
function audioFileLoader(fileDirectory) {
var soundObj = {};
var getSound = new XMLHttpRequest();
soundObj.fileDirectory = fileDirectory;
getSound.open("GET", soundObj.fileDirectory, true);
getSound.responseType = "arraybuffer";
getSound.onload = function() {
audioContext.decodeAudioData(getSound.response, function(buffer) {
soundObj.soundToPlay = buffer; // Property assigned buffer
});
};
getSound.send();
return soundObj;
}
You can now create a playback method that is an extension of soundObj to play back the
buffers.
function audioFileLoader(fileDirectory) {
});
};
getSound.send();
soundObj.play = function(time) {
return soundObj;
The time argument of the play function determines the number of seconds you want the
audio file to play into the future. The logical expression (audioContext.currentTime +
time || audioContext.currentTime) is used to determine whether the time argument
is empty and, if it is, then the start method does not add additional seconds to the value of
audioContext.currentTime. When no arguments are set, the sound plays immediately.
soundObj.play = function(time) {
The stop method lets users determine when a sound will stop playback.
soundObj.stop = function(time) {
playSound.stop(audioContext.currentTime + time ||
audioContext.currentTime);
}
This code works, but it reveals a new potential problem. If you want to load multiple files, you
have to type out an audioFileLoader invocation for each one, like this:
var kick = audioFileLoader("sounds/kick.mp3");
var snare = audioFileLoader("sounds/snare.mp3");
var hihat = audioFileLoader("sounds/hihat.mp3");
var shaker = audioFileLoader("sounds/shaker.mp3");
One way to mitigate this additional repetition is to create a helper function that loops through
an object that contains a collection of audio file directories and invoke the audioFileloader
on each file. You can then return the object. This will allow each sound to be accessible via its
property name. The following code demonstrates this:
function audioBatchLoader(obj) {
for (var prop in obj) {
obj[prop] = audioFileLoader(obj[prop]);
}
return obj;
}
You now have a working library to load multiple audio files. The following code sets an event
listener on the window. If you click it, you will hear the loaded sound play.
var sound = audioBatchLoader({
kick: "sounds/kick.mp3",
snare: "sounds/snare.mp3",
hihat: "sounds/hihat.mp3",
shaker: "sounds/shaker.mp3"
});
window.addEventListener("mousedown", function() {
sound.snare.play();
});
Summary
In this chapter, you learned the basics of how to think about abstraction, while creating a new tool
for loading and playing back multiple audio files. In the next few chapters, you will learn how to
manipulate audio via the node graph using various effects.
14 The Node Graph and Working with
Effects
Up to this point, the topic of the node graph has only been partially described and has been used
mostly as a tool to explain related concepts. In this chapter, you will learn how to work with the
node graph to develop custom signal chains for complex audio applications. The Web Audio API
includes many built-in objects that let you manipulate audio in creative ways. You also learn how
to include these objects in your applications and use them to create customized effects.
The following code is an example of creating two oscillators and connecting each one to an
independent gain node for individual volume control. These are summed to a third gain node,
which is connected to the audioContext.destination.
//________________________________BEGIN create sawtooth oscillator
var oscSaw = audioContext.createOscillator();
oscSaw.type = "sawtooth";
oscSaw.frequency.value = 118;
oscSaw.start(audioContext.currentTime);
//________________________________END create sawtooth oscillator
A Real-World Example
Assume you want to apply a low-pass (also called lowpass) filter to an oscillator. (A low-pass
filter is a filter that only allows signals below a certain frequency to pass.) To do this, you first
research the Web Audio API documentation to see whether this type of filter is supported. You
can search the specification directly at: https://www.w3.org/TR/webaudio. An alternative
reference (and one that is a bit more readable) is the Mozilla Developer Network documentation
at: https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API.
When researching, do a search for “filters” or “lowpass.” In the results, you will discover that
there is a specialized node called a biquad filter that is dedicated to audio filtering. This node
includes a property named type that you can set to lowpass. As the value implies, this filter
type is used to apply a low-pass filter to an input source. To apply this to your application, you
first invoke the createBiquadFilter() method, which returns an object that you store in a
variable. You then connect an input source, such as an oscillator or array buffer, to this object
using the connect method.
var audioContext = new AudioContext();
The final step is to define any additional properties or methods to customize the effect.
Properties or methods that allow you to customize the behavior of nodes are called audio params
(short for audio parameters). In the previous example, type is an audio param. The following
code sets another audio param named frequency to the value 250. This defines where the low-
pass filter begins to cut off in the frequency spectrum.
var audioContext = new AudioContext();
Summary
In this chapter, you were formally introduced to the node graph and how to create custom signal
chains using input sources and effects nodes. In the next few chapters, you will build on this
knowledge and explore the specifics of some of these effects nodes.
15 The Biquad Filter Node
One of the most common ways to manipulate sound is by boosting or attenuating a range of
frequencies using audio filters. A familiar example of this is the use of audio equalizers to
brighten or muffle a sound. The Web Audio API has a node named BiquadFilter that allows
you to create different types of audio filters that can be connected together to create various forms
of equalizers. In this chapter, you will learn how to use the BiquadFilter node, and in the
process, you will create a seven-band graphic equalizer and a single-band parametric equalizer.
Once you create the object, you can connect an input source to it. The following example
connects an oscillator to the object.
var audioContext = new AudioContext();
var osc = audioContext.createOscillator();
var filter = audioContext.createBiquadFilter();
osc.connect(filter); // connect input source to filter
osc.start(audioContext.currentTime);
filter.connect(audioContext.destination); /*connect filter to
audioContext.destination*/
Filter Types
BiquadFilter contains a property named type that defines the type of filter the node
behaves like. If you do not explicitly set the type property, its default value is lowpass. You
can see this in the console.log() output in the following code:
var audioContext = new AudioContext();
var osc = audioContext.createOscillator();
var filter = audioContext.createBiquadFilter();
filter.frequency.value = 250;
console.log(filter.type); // default is lowpass
osc.connect(filter);
osc.start(audioContext.currentTime);
filter.connect(audioContext.destination);
To explicitly set the type property to lowpass, you write the following code:
filter.type = "lowpass";
The type value of a BiquadFilter node determines if it has two additional properties:
gain and Q. The gain.value property allows you to boost or attenuate
frequency.value. The Q.value property represents the bandwidth of the frequency value.
Bandwidth represents the reach by which neighboring frequencies are affected in relation to
changes made to the gain of the selected frequency. The following images demonstrate the
difference between a narrow bandwidth setting and a wide bandwidth setting of a 1 kHz
frequency using a peaking filter.
Graphic EQ
The following diagram and code show how to create a seven-band graphic equalizer. You do this
by chaining a series of BiquadFilter nodes together and setting their type properties to
peaking. Keep in mind that the only parameter the user of a graphic equalizer should be
allowed to change is the gain of each filter. The input and output source for this example is
abstracted using a function named multibandEQ.
inputConnection.connect(filter1);
filter1.connect(filter2);
filter2.connect(filter3);
filter3.connect(filter4);
filter4.connect(filter5);
filter5.connect(filter6);
filter6.connect(filter7);
filter7.connect(outputConnection);
The code files for this chapter include versions of both the graphic and parametric equalizers
with user interface controls. These applications allow you to toggle the playback of a song and
change parameters of the BiquadFilter nodes in real time by using the interactive sliders.
Parametric EQ
You can design a parametric equalizer in a similar way to the graphic equalizer by chaining a
series of BiquadFilter nodes together and setting their type properties to peaking. The
primary difference of the parametric equalizer is that the frequency, gain, and bandwidth are
modifiable by the user. Keep in mind that with multiband parametric equalizers, the filter type
may have multiple options available. To keep the code simple and short, the following example
shows how to create a single-band parametric equalizer with type set to the value peaking.
The input and output source in this code is abstracted using a function named parametricEQ.
var parametricEQ1 = audioContext.createBiquadFilter();
parametricEQ1.type = "peaking";
parametricEQ1.gain.value = 0; // allow the user to change this
parametricEQ1.Q.value = 1; // allow the user to change this
parametricEQ1.frequency.value = 1000;
function parametricEQ(inputConnection, outputConnection) {
inputConnection.connect(parametricEQ1);
parametricEQ1.connect(outputConnection);
}
Summary
In this chapter, you learned about the BiquadFilter node and how to use it to create custom
equalizers and filter arrangements. Keep in mind that the examples here are kept simple, and like
the node graph itself, your filter arrangements can be as complex as you want to make them. In the
next chapter, you will learn about another signal processing node: the convolver node.
16 The Convolver Node
In this chapter, you will learn how to use the convolver node. The convolver allows you to apply
reverberation to node graph input sources by referencing a special kind of audio file called an
impulse response.
Convolution Reverb
When an acoustic sound is created, its characteristics are shaped by its immediate environment.
This is due to sound waves bouncing off and around various obstacles. These obstacles can be
made of different materials that affect the sound in different ways. The result of sound emanating
from a small room has different characteristics than sound emanating from a large room. Because
the human ear can hear these differences, when this information is transmitted to the brain, we
perceive these characteristics as room ambience. Modern advancements in digital audio
technology allow us to record the ambience of any real-world environment and apply it to any
digital audio signal directly. These recorded ambiences are stored as a special file called an
impulse response. An impulse response file is made by recording a single sound burst in an
environment, which could be white noise, a sine wave sweep, or even a balloon pop. This
recording is then run through a special digital algorithm to create a single file called an impulse
response. This impulse response file is combined or convolved with another input source to give
the targeted sound the spacial characteristics of the room that the impulse is modeled from.
The format of impulse response files can be any audio file type including WAV, MP3, AIFF, or
OGG. However, to use them with the Web Audio API, impulse response files must be in a
browser-compatible audio format. For this chapter, we use WAV files because they are of higher
quality than MP3 files. And because impulse response files are small, load time is not a concern.
getSound.onload = function() {
audioContext.decodeAudioData(getSound.response, function(buffer) {
impulseResponseBuffer = buffer;
});
};
getSound.send();
After the file is stored in a buffer, the next step is to wire up the necessary nodes to apply the
effect to an input source. To integrate the impulse response into the node graph configuration, you
must first create a convolver node using audioContext.createConvolver() and store
the returned object in a variable.
var convolver = audioContext.createConvolver();
You then assign the loaded impulse response buffer to the buffer property of the object.
convolver.buffer = impulseResponseBuffer;
Next, you connect any input source you want to the convolver node. Here is an example of
connecting an oscillator.
var osc = audioContext.createOscillator();
var convolver = audioContext.createConvolver();
osc.type = "sawtooth";
convolver.buffer = impulseResponseBuffer;
osc.connect(convolver);
convolver.connect(audioContext.destination);
osc.start(audioContext.currentTime);
The following HTML and JavaScript code combines the impulse file loader, node graph
connections, and JQuery DOM selectors to allow you to play the oscillator by clicking an HTML
button and holding it. This allows you to hear the reverberation effect more explicitly because the
reverb tail is audible after removing your finger from the mouse button and stopping the
oscillator.
HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript" src="https://ajax.googleapis.
com/ajax/libs/jquery/2.1.0/jquery.js"></script>
<script src="js/app.js"></script>
</head>
<body>
<button>Oscillation</button>
</body>
</html>
JavaScript
"use strict";
var audioContext = new AudioContext();
var impulseResponseBuffer;
var getSound = new XMLHttpRequest();
getSound.open("get", "sounds/impulse.wav", true);
getSound.responseType = "arraybuffer";
getSound.onload = function() {
audioContext.decodeAudioData(getSound.response, function(buffer) {
impulseResponseBuffer = buffer;
});
};
getSound.send();
/*___________________________________________BEGIN playback
functionality*/
function playback() {
var convolver = audioContext.createConvolver();
osc = audioContext.createOscillator();
osc.type = "sawtooth";
convolver.buffer = impulseResponseBuffer;
osc.connect(convolver);
convolver.connect(audioContext.destination);
osc.start(audioContext.currentTime);
}
$(function() {
$("button").on("mousedown", function() {
playback();
});
$("button").on("mouseup", function() {
osc.stop();
});
});
Controlling the Amount of Reverberation
In the previous code example, the amount of reverb applied to the oscillator is fixed at 100
percent. If you want to make the effect variable, which allows you to control how much of the
effect is applied to the input source, you can do so by splitting the input source with a gain node
and routing one split to the convolver node prior to connecting it to the destination. You then
connect the other split directly to the destination. You use gain.value to blend the amount of
the effect you want to hear.
The following diagram and node graph configuration code demonstrate the splitting operation.
osc = audioContext.createOscillator();
osc.type = "sawtooth";
convolver.buffer = impulseResponseBuffer;
osc.connect(convolver);
convolver.connect(gain);
gain.gain.value = 0.2;
gain.connect(audioContext.destination);
osc.connect(audioContext.destination);
osc.start(audioContext.currentTime);
Summary
In this chapter, you learned how to use the convolver node to apply an impulse response file to an
input source. You also learned how to use gain nodes to control the amount of the effect you want
to hear. In the next chapter, you will learn how to modify the panning of stereo input sources and
how to create sophisticated routing schemes using the channel and merger nodes.
17 Stereo Panning, Channel Splitting, and
Merging
The Web Audio API includes a stereo panner node that lets you pan input sources to any part of
the stereo field. It also includes nodes that let you split multichannel audio files into separate
channels as well as merge multichannel input sources into a specified output channel. In this
chapter, you will learn how to use these nodes to manipulate multichannel input sources.
You can then connect any input source to the node and use pan.value to set the location in
the stereo field where you want to place the sound. The pan.value property setting is a number
between 1 and −1, where 1 represents a 100 percent pan to the right and −1 represents a 100
percent pan to the left. In the following example, an oscillator is connected to a stereo panner
node and is set 50 percent to the left.
var oscillator = audioContext.createOscillator();
var stereoPanner = audioContext.createStereoPanner();
stereoPanner.pan.value = -0.5;
oscillator.connect(stereoPanner);
stereoPanner.connect(audioContext.destination);
oscillator.start(audioContext.currentTime);
The stereoPanner() uses an equal power algorithm to pan input sources. This means that
when a stereo input source is panned, the audio content on the attenuated side is summed with the
audio on the amplified side.
The Channel Splitter
If you want to isolate the individual channels of a multichannel input source or do not want your
stereo input sources to be subjected to an equal power algorithm, you must use the channel
splitter. This node isolates any channel of a multichannel input source for further processing. This
applies to both stereo and other multichannel audio input sources, such as 5.1 surround files. To
create a channel splitter, you invoke the createChannelSplitter() method with a single
argument and store the returned object in a variable. The argument value is the number of
channels of the audio source material that you intend to connect to the splitter. If no argument is
specified, the default is 6. In the following example, a stereo file is split, so the argument is set to
2.
var splitter = audioContext.createChannelSplitter(2);
To use the channel splitter, you connect input sources to it and then connect the splitter to other
nodes. When connecting the splitter to a destination node, you specify the channel of the input
source to connect to in the second argument of the connect() method. This argument is a
number that represents the channel as an index value. The following chart displays the index for
each channel of a six-channel input source.
The following code shows the correspondence between the channel index argument and its
respective channel type.
stereoInputSource.connect(splitter);
splitter.connect(audioContext.destination, 0); /*outputs left
side/channel of stereo input source*/
splitter.connect(audioContext.destination, 1); /*outputs right
side/channel of stereo input source*/
The following code shows how to modify the gain value of individual left and right channels
of a stereo input source. In other words, this configuration is the opposite of an equal power
panning algorithm.
var splitter = audioContext.createChannelSplitter(2);
var pannerLeft = audioContext.createStereoPanner();
var pannerRight = audioContext.createStereoPanner();
var left = audioContext.createGain();
var right = audioContext.createGain();
sound = audioContext.createBufferSource();
sound.loop = true;
sound.buffer = bufferSource;
sound.connect(splitter);
splitter.connect(left, 0); //___connect left channel to gain node
splitter.connect(right, 1); //__connect right channel to gain node
left.gain.value = leftVal; /*_________independent left channel
control*/
right.gain.value = rightVal; /*________independent right channel
control*/
left.connect(pannerLeft);
pannerLeft.pan.value = -1;
pannerRight.pan.value = 1;
right.connect(pannerRight);
pannerLeft.connect(audioContext.destination);
pannerRight.connect(audioContext.destination);
sound.start(audioContext.currentTime + time || audioContext.
currentTime);
When connecting an input source to a channel merger, you must specify the output channel
using the third argument of the connect method.
inputSource.connect(merger, 0, 1); /*outputs all channels of
inputSource to right channel*/
If you connect an audio input source, such as an audio buffer source node, directly to a channel
merger node, there is no reason to set the second argument of the connect method to a value
other than 0. This is because the merger node has a single output.
audioBufferSource.connect(merger, 0, 1);
If the input of a channel merger is a channel splitter, the second argument of the connect
method is the channel of the input source sent to the merger.
var channelSplitter = audioContext.createChannelSplitter();
var channelMerger = audioContext.createChannelMerger();
var sound = audioContext.createBufferSource();
sound.buffer = audioBuffer;
sound.connect(channelSplitter);
channelSplitter.connect(channelMerger, 0, 0); /*The left channel
of playSound is connected to the channel merger*/
channelMerger.connect(audioContext.destination);
Summary
In this chapter, you learned how to apply stereo panning to audio input sources. You also learned
how to work with the channel splitter and channel merger nodes. In the next chapter, you will
explore how to create delay effects using the delay node.
18 The Delay Node
In the world of creative audio, delays are a common method used to create time-based effects. In
this chapter, you will learn how to use the delay node to create the most common delay effects:
echo, slap back, and ping-pong.
If you listen to the result of the previous example, you will notice that it does not provide the
repetitive echo delay effect that is typical of an effects processor. This is because the only thing
the delay node does is pause the audio from playing for a set amount of time. If you want a
repetitive echo effect, you must create it.
The following code implements the configuration shown in the above figure.
//___________________________________________________BEGIN setup
//____________________________________________________END setup
sound.connect(splitter);
sound.connect(audioContext.destination);
splitter.connect(leftDelay, 0);
leftDelay.delayTime.value = 0.5;
leftDelay.connect(leftFeedback);
leftFeedback.gain.value = 0.6;
leftFeedback.connect(rightDelay);
splitter.connect(rightDelay, 1);
rightDelay.delayTime.value = 0.5;
rightFeedback.gain.value = 0.6;
rightDelay.connect(rightFeedback);
rightFeedback.connect(leftDelay);
leftFeedback.connect(merger, 0, 0);
rightFeedback.connect(merger, 0, 1);
//___________________________________________BEGIN output
merger.connect(audioContext.destination);
//___________________________________________END output
sound.start(audioContext.currentTime);
Summary
The delay node by itself is not complicated or difficult to use, but when combined with other
nodes it can be a powerful tool for the creation of interesting audio effects. In the next chapter,
you will continue exploring the node graph and learn how to apply dynamic range compression to
audio input sources.
19 Dynamic Range Compression
In this chapter, you will learn about the dynamics compressor node. This node allows you to
apply dynamic range compression to audio input sources.
The object provides you with a collection of five properties that affect the dynamic range of an
audio input source. A sixth property called reduction is also available, but it does not affect
the input source in any way. The reduction property is used exclusively to output a reduction
value. These properties are briefly described in the following chart. All properties except
reduction take a number as their assignment. The reduction property provides only a
readout value.
The following code demonstrates how to apply the dynamics compressor node to an audio
input source. In this example, for every 12 dB the signal surpasses a threshold of −40 dB, its
output is increased by 1 dB.
//___________________________________________________BEGIN setup
//____________________________________________________END setup
sound.connect(compressor);
compressor.threshold.value = -40;
compressor.ratio.value = 12;
//___________________________________________BEGIN output
compressor.connect(audioContext.destination);
//___________________________________________END output
sound.start(audioContext.currentTime);
Anyone familiar with the world of creative audio will immediately be familiar with every
property available to the dynamics compressor node except one: reduction. The
reduction property, specific to the Web Audio API, outputs a numeric value representing the
amount of reduction the compressor is imposing on the input source. The following code uses
setInterval() to allow you to see the change in reduction value as an audio input source is
compressed.
//___________________________________________________BEGIN setup
var compressor = audioContext.createDynamicsCompressor();
sound.connect(compressor);
compressor.threshold.value = -40;
compressor.ratio.value = 12;
//___________________________________________BEGIN output
compressor.connect(audioContext.destination);
//___________________________________________END output
sound.start(audioContext.currentTime);
window.setInterval(function() {
console.log(compressor.reduction.value);
}, 50);
Summary
Using the dynamics compressor node is not complicated. It contains all the basic parameters
needed to modify the dynamic range of any input source connected to it.
In the next chapter you will learn how to work with time in the Web Audio API.
20 Time
In this chapter, you will learn how to work with time to schedule Web Audio API sound playback
points, how to create loops, and how to automate parameter changes.
When you play an audio event, the Web Audio API requires you to schedule it. Remember that
the unit you use for time value scheduling is seconds. If you want to schedule an event
immediately, you can use the currentTime property of the audio context.
You have already had some exposure to scheduling the playback of sounds in previous
chapters, such as in the following example code:
sound.start(audioContext.currentTime); // Play immediately
sound.start(audioContext.currentTime + 2); /*Play audio buffer two
seconds into the future*/
The third argument sets how much of the sound will play. If you have a sound that is 4 seconds
long and you only want to hear the first 2 seconds, then you set the third argument to 2.
sound.start(audioContext.currentTime,0, 2);
Looping Sounds
To loop sounds, you set the loop property of an audio buffer source node to true. To set the
start point of a loop, you use the property loopStart. To set the end point of a loop, you use
the property loopEnd.
sound.loop = true;
sound.loopStart = 1; /*Set loop point at one second after
beginning of playback*/
sound.loopEnd = 2; /*Set loop end point at two seconds after
beginning of playback*/
Sometimes when trying to discern playback and loop points, it is useful to know the length of
an audio file. You can get this information using a property of the sound buffer named
duration.
var sound = audioContext.createBufferSource();
sound.buffer = buffer;
sound.buffer.duration; // length in seconds of audio file
Included in the code examples for this chapter is an application that allows you to modify the
playback and loop points of an audio file in real time using interactive sliders.
Update Your Audio Loader Library
The play() method of your audio loader library is not designed to access the second and third
arguments of the start method. You can make these arguments available with the following
modifications to your code:
soundObj.play = function(time, setStart, setDuration) {
playSound = audioContext.createBufferSource();
playSound.buffer = soundObj.soundToPlay;
playSound.connect(audioContext.destination);
playSound.start(audioContext.currentTime + time || audioContext.
currentTime, setStart || 0, setDuration || soundObj.soundToPlay.
duration);
};
The Web Audio API comes with a collection of methods that allow you to schedule audio
parameter values immediately or at some point in the future. The following code shows a list of
these methods.
setValueAtTime(arg1,arg2)
exponentialRampToValueAtTime(arg1,arg2)
linearRampToValueAtTime(arg1,arg2)
setTargetAtTime(arg1,arg2,arg3)
setValueCurveAtTime(arg1,arg2,arg3)
You can use these methods in place of setting the value property of an audio parameter.
osc.frequency.value = 100; // Set value directly
osc.frequency.setValueAtTime(arg1,arg2); /*Set value with audio
parameter method*/
To use any of the other audio parameter methods that are described next, you must first
initialize their settings using setValueAtTime(). This is shown in the code examples for
each method.
The second argument represents when you want the changes to begin, and the third argument is
the time span you want the changes to take place within. The following code demonstrates this by
toggling the frequency of an oscillator from 100 to 500 Hz and back again over the course of 3
seconds. This creates a wobble effect.
var waveArray = new Float32Array(10);
waveArray[0] = 100;
waveArray[1] = 500;
waveArray[2] = 100;
waveArray[3] = 500;
waveArray[4] = 100;
waveArray[5] = 500;
waveArray[6] = 100;
waveArray[7] = 500;
waveArray[8] = 100;
waveArray[9] = 500;
Summary
In this chapter, you learned the fundamentals of working with time. You learned how to loop and
schedule sound playback, as well as how to schedule parameter value changes. In the next
chapter, you will learn how to create audio visualizations using the Analyser node.
21 Creating Audio Visualizations
In this chapter, you will learn how to use the Analyser node to create a spectrum analyzer that
displays real-time amplitude information of audio signals across a collection of frequency bands.
The Web Audio API includes a node named Analyser that gives you real-time frequency and
time domain information about audio input sources. This information can be used to create custom
visual representations of audio signals that include (but are not limited to) spectrum analyzers,
phase scopes, and waveform renders.
When all bits are on, a byte has a value of 255. This allows for 256 total possible values (0–
255).
JavaScript/JQuery
"use strict";
$(function() {
var audioContext = new AudioContext();
var analyzer = audioContext.createAnalyser();
var osc = audioContext.createOscillator();
var frequencyData = new Uint8Array(analyzer.frequencyBinCount);
//___Create array
analyzer.getByteFrequencyData(frequencyData);
//______________________Store frequency data
console.log(frequencyData.length);
console.log(frequencyData);
osc.frequency.value = 120;
osc.connect(analyzer);
analyzer.connect(audioContext.destination);
osc.start(audioContext.currentTime);
analyzer.fftSize = 2048;
console.log(analyzer.frequencyBinCount); // 1024
//________________________________BEGIN Visualization
function update() {
requestAnimationFrame(update);
analyzer.getByteFrequencyData(frequencyData);
update();
//_______________________________END visualization
});
HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript" src="js/jquery.js"></script>
<script src="js/app.js"></script>
<link rel="stylesheet" href="css/app.css" type="text/css">
</head>
<!--______________________________________________________BEGIN
APP-->
<body>
<p class="bin-count">
Bin count:<b class="bin-count-number"></b>
</p>
<div class="app">
</div>
</body>
<!--______________________________________________________END
APP-->
</html>
CSS
.app{
position: relative;
margin: 10px;
span{
display:inline-block;
font-size:14px;
color:rgba(128, 128, 128, 0.5);
margin:2px;
.bin-count{
position:absolute;
left:30%;
float:right;
font-size:2em;
height:50px;
The Analyser interface enables you to perform various FFTs on the audio stream. The FFT
used to create a spectrum analyzer transforms the time domain of the audio signal into normalized
(or limited) frequency-domain data. This is done by chopping the original audio signal into parts,
typically called bins, and performing an analysis and transformation on each part.
The size of the FFT is stored in the fftSize property of the Analyser node and the
default value is 2048. The allowed values are any power of 2 between 32 and 2048. If you set it
wrong, you will get an error. The number of bins available is one half of the fftSize property
and is accessible by a read-only property of the Analyser node named
frequencyBinCount.
analyzer.fftSize = 2048;
console.log(analyzer.frequencyBinCount); // 1024... or half of fftSize
Each bin is designated a range of frequencies called a band, and the following formula
determines the range of each band:
(Sample Rate)/(FFT Size) = (Band Size)
Example:
44,100/2048 = 21.533203125
There are two kinds of typed arrays that the Analyser node is designed to work with:
Float32Array and Uint8Array. The index values of a Float32Array are always a
decimal number between 0 and 1. The index values of a Uint8Array are limited to 8 bits of
information and will always be an integer between 0 and 255. Using a Float32Array allows
for up to 32 bits of information and gives you more precision but is more resource intensive. This
is in contrast to Uint8Array, which is more resource efficient but less precise. This
application uses a Uint8Array. A Float32Array or Uint8Array must be created using
the keyword new.
In the following code, the Uint8Array is invoked with a single argument that determines the
number of indexes it will have by using analyzer.frequencyBinCount.
var frequencyData = new Uint8Array(analyzer.frequencyBinCount);
console.log(frequencyData.length); // 1024
You now store the frequency domain data in the array using getByteFrequencyData().
analyzer.getByteFrequencyData(frequencyData);
The bars variable selects all div elements and is used later in the code.
bars = $(".app > div");
update();
To create the vertical frequency bars, the for loop updates the CSS height property of each
div stored in the bar variable. The value given to each div is a pixel representation of the
current frequencyData array index. This value is between 0 and 255 px.
function update() {
requestAnimationFrame(update);
analyzer.getByteFrequencyData(frequencyData);
update();
The user interface of the spectrum analyzer is designed to create a div for all bins. You can
change the size of the FFT to lower the bin count.
analyzer.fftSize = 64;
The application you created in this chapter works with frequency-domain data. If you want to
work with time-domain data, the Analyser node has two methods that let you copy it to a typed
array. The first method is getByteTime DomainData () and is for use with a
Uint8Array(). The second method is getFloatTimeDomainData () and is for use
with Float32Array(). To store the time domain data in a Uint8Array(), you write the
following code:
var frequencyData = new Uint8Array(analyzer.frequencyBinCount);
analyzer.getByteTimeDomainData(frequencyData);
Summary
In this chapter, you learned about the Analyser node and created a frequency spectrum analyzer
application.
Up until now, when adding new nodes that affect audio buffers, you have applied the changes
to a single node graph that affects all audio buffer input sources. In the next chapter, you will
update the audio loader library that you created in Chapter 13 to allow users to create customized
node graphs for individual audio buffers.
22 Adding Flexibility to the Audio Loader
Abstraction
In this chapter, you will add flexibility to the audio loader abstraction and give users
independently customizable node graphs for audio buffer input sources. In its current state, the
audio loader library you created in Chapter 13 only allows you to create one universal node
graph configuration. So any files that you load have to conform to this configuration. This is
undesirable for two reasons. The first is that when you create a library, you don’t want the user to
have to modify its internals to get the functionality they want. The second reason is that it is useful
to have the choice to apply completely different effects to different audio input sources, which
requires node configurations that are independently customizable.
var soundData = {
kick: "sounds/kick.mp3",
snare: "sounds/snare.mp3",
//________________________________________BEGIN custom node graph
nodeGraph: function nodeGraph(sound) {
var gain = audioContext.createGain();
gain.gain.value = 1;
sound.connect(gain);
gain.connect(audioContext.destination);
}
//________________________________________END custom node graph
}
var sound = audioBatchLoader(soundData); //Takes the object as
argument
sound.kick.play(); // Sound plays using custom node graph
sound.snare.play(); // Sound plays using custom node graph
This interface allows you to add one method to the object; and this method then sets the node
graph configuration for all sound files referenced as properties of that object.
Next, create a directory named “sounds” and place an MP3 file named “snare” in it (this file is
available in the downloadable code examples). Copy the following code into the app.js file.
"use strict";
var sound = audioFileLoader("sounds/snare.mp3", function(sound){
var gain = audioContext.createGain();
gain.gain.value = 0.2;
sound.connect(gain);
gain.connect(audioContext.destination);
});
window.addEventListener("mousedown", function() {
sound.play();
});
window.addEventListener("mouseup", function() {
sound.stop();
});
If you run the previous code, it will not work. In the audiolib.js file you will now modify
the function named audioFileLoader so that the previous code works. These modifications
will let users load single files, each of which has a unique node graph. Once this works, we will
go over how to modify audioBatchLoader to load multiple files.
In your audiolib.js file, modify the code to include a callback function like the following
example:
var audioContext = new AudioContext();
function audioFileLoader(fileDirectory, callback) {
var playSound = undefined;
var soundObj = {};
soundObj.fileDirectory = fileDirectory;
var getSound = new XMLHttpRequest();
getSound.open("GET", soundObj.fileDirectory, true);
getSound.responseType = "arraybuffer";
getSound.onload = function() {
audioContext.decodeAudioData(getSound.response, function(buffer) {
soundObj.soundToPlay = buffer;
});
}
getSound.send();
soundObj.play = function(time) {
playSound = audioContext.createBufferSource();
playSound.buffer = soundObj.soundToPlay;
playSound.connect(audioContext.destination);
playSound.start(audioContext.currentTime + time ||
audioContext.currentTime, setStart || 0, setDuration ||
soundObj.soundToPlay.duration);
callback(playSound);
soundObj.stop = function(time) {
}
return soundObj;
};
In the previous code, the callback now fulfills the role of a customizable node graph and the
code will now work. However, there is still one problem: If the user does not use a callback, then
an error results.
var sound = audioFileLoader("sounds/snare.mp3"); // ERROR!
This error can be dealt with by simply using a conditional to check if the callback is a
function. If the conditional returns false (because the user didn’t set it), a default node graph is set
in its place.
To do this, modify the code as follows:
soundObj.play = function(time) {
playSound = audioContext.createBufferSource();
playSound.buffer = soundObj.soundToPlay;
playSound.start(audioContext.currentTime + time || audioContext.
currentTime);
callback(playSound);
if (typeof callback === "function") {
return callback(playSound);
}else {
return playSound.connect(audioContext.destination);
}
This code now works whether the function is invoked with a callback or not.
Modifying audioBatchLoader
You will now edit the audioBatchLoader function to check if its parameter object contains a
method, and if it does, the method is set as the callback of audioFileLoader. This code
applies a custom node graph to a group of files.
function audioBatchLoader(obj) {
var callback = undefined;
var prop = undefined;
}
return obj;
}
}
});
window.addEventListener("mousedown", function() {
sound.snare.play();
});
window.addEventListener("mouseup", function() {
sound.snare.stop();
});
One thing you should be aware of is that the object that the audioBatchLoader takes as an
argument is intended to have only one method. If it has more than one method, then one of them is
overwritten. When using a for-in loop, the properties and methods of the targeted object are not
returned in any particular order. Because of this, you cannot know which method is used and
which one is overwritten until the sound is played back. For this reason, you might want to write
an error check to throw an error for argument objects that have more than one method. This is left
up to you to implement.
Summary
In this chapter, you added additional flexibility to your audio loader library and in the process
you were exposed to a real-world example of how callback functions can be useful when
designing a library. In the next chapter you will learn how to build an interactive music sequencer.
23 Building a Step Sequencer
Music applications, like sequencers and drum machines, allow users to record, edit, and play
back sounds as a collection of organized note arrangements. Due to the nature of the Web Audio
API and its relationship to the DOM, music sequencing applications are a challenge to create. In
this chapter, you will learn why this is so and how to meet the challenge by building a basic drum
pattern step sequencer.
The Problem
The Web Audio API lets you schedule events immediately or in the future. A problem with this
approach is that once an event is scheduled, it cannot be unscheduled. So for example, the
following code schedules three drum sounds to play in an 8th note pattern for four bars.
var kick = audioFileLoader("sounds/kick.mp3");
var snare = audioFileLoader("sounds/snare.mp3");
var hihat = audioFileLoader("sounds/hihat.mp3");
var counter = 1;
window.setInterval(function() {
if (counter === 8) {
counter = 1;
} else {
counter += 1;
}
if (counter) {
hihat.play();
}
if (counter === 3 || counter === 7) {
snare.play();
}
if (counter === 1 || counter === 5) {
kick.play();
}
}, eighthNoteTime);
The problem with this approach is that both the setTimeout and setInterval methods
have timings that are imprecise and unstable. There are two reasons for this. The first is that the
smallest unit of time available to these methods is an integer of 1 millisecond, which is not
precise enough for audio sample-level values like 44.100 kHz. The other problem is that unlike
the Web Audio API timing clock, these methods can be interrupted by ancillary browser activity
like page rendering and redraws. Although you might expect setInterval or setTimeout
to run at every nth millisecond, depending on factors outside your control, the value will likely be
larger and audibly noticeable.
The Solution
The solution to the problem is to create a relationship between the Web Audio API timing clock
and the browser’s internal setTimeout method to create a look-ahead mechanism that
recursively loops and checks if events will be scheduled at some time in the future. If this is the
case, the scheduling happens and the event(s) takes place. This gives you enough leeway to cancel
events at the last moment if needed.
One thing to keep in mind is that because setTimeout is inherently unstable, we know that
this relationship will always have an unstable aspect to it. Whether or not this approach is stable
enough for your applications is for you to decide. One thing we can be certain of is that it is much
more accurate than using setInterval or setTimeout on its own.
How It Works
The basis for the relationship between the Web Audio API timing clock and the browser’s
internal setTimeout method is expressed in the following code:
var audioContext = new AudioContext();
var futureTickTime = audioContext.currentTime;
function scheduler() {
if (futureTickTime < audioContext.currentTime + 0.1) {
futureTickTime += 0.5; //____can be any time value. 0.5 happens
to be a quarter note at 120 bpm
console.log(futureTickTime);
}
window.setTimeout(scheduler, 0);
}
scheduler();
The way the previous code works is that the setTimeout function loops recursively, and
upon each iteration, a conditional checks whether the value of futureTickTime is within a
tenth of a second of the audioContext.currentTime. If this evaluates to true then
futureTickTime is incremented by 0.5, which is half a second in “Web Audio Time.” The
futureTickTime variable remains set at this value until audioContext.currentTime
“catches up with it” once again. Then within a tenth of a second, futureTickTime is
incremented by a half-second into the future. This pattern continues for as long as the function is
allowed to run.
Because a half-second translates to a quarter note at 120 beats per minute, the following code
uses this information to create a 1/4th note timing count that is logged to the console.
var futureTickTime = audioContext.currentTime;
var counter = 1;
function scheduler() {
if (futureTickTime < audioContext.currentTime + 0.1) {
console.log("This is beat: " + counter);
futureTickTime += 0.5; /*____can be any time value. 0.5 happens
to be a quarter note at 120 bpm*/
counter += 1;
if (counter > 4) {
counter = 1;
}
}
window.setTimeout(scheduler, 0);
}
scheduler();
The following code builds on the previous example and plays an oscillator on each count. The
oscillator is connected to a gain node named metronomeVolume which is connected to the
destination. The gain node is added because the final application in this chapter uses it to toggle
the oscillator volume on and off.
var futureTickTime = audioContext.currentTime;
var counter = 1;
var osc = audioContext.createOscillator();
var metronomeVolume = audioContext.createGain();
function playMetronome(time) {
osc = audioContext.createOscillator();
osc.connect(metronomeVolume);
metronomeVolume.connect(audioContext.destination);
osc.start(time);
osc.stop(time + 0.1);
function scheduler() {
if (futureTickTime < audioContext.currentTime + 0.1) {
counter += 1;
if (counter > 4) {
counter = 1;
}
}
window.setTimeout(scheduler, 0);
}
scheduler();
Changing Tempo
If you want to change the tempo, you have to change the time relationship between events. You
can do this by altering when events are scheduled to start with the futureTickTime variable.
The following formula is useful for converting beats (quarter notes) to seconds.
var tempo = 120.0; // tempo (in beats per minute);
var secondsPerBeat = (60.0 / tempo);
The application you build assumes the use of a 16th note grid. You can design it with any beat
division(s) you want, but for simplicity it is hard-coded with 16 notes. The following code
converts the futureTickTime variable from a time value that represents a quarter note to a
time value that represents a 16th note. The oscillator is also modified to play a different frequency
on the downbeat.
var futureTickTime = audioContext.currentTime;
var counter = 1;
var tempo = 120;
var secondsPerBeat = 60 / tempo;
var counterTimeValue = (secondsPerBeat / 4); //___16th note
var osc = audioContext.createOscillator();
var metronomeVolume = audioContext.createGain();
function playMetronome(time) {
osc = audioContext.createOscillator();
osc.connect(metronomeVolume);
metronomeVolume.connect(audioContext.destination);
osc.frequency.value = 500;
osc.start(time);
osc.stop(time + 0.1);
}
function scheduler() {
if (futureTickTime < audioContext.currentTime + 0.1) {
console.log("This is 16th is: " + counter);
playMetronome(futureTickTime);
futureTickTime += counterTimeValue;
if (counter === 1) {
osc.frequency.value = 500;
} else {
osc.frequency.value = 300;
}
counter += 1;
if (counter > 16) {
counter = 1;
}
}
window.setTimeout(scheduler, 0);
}
scheduler();
You can now change the tempo by modifying the tempo variable.
//_____________________________________________BEGIN metronome
function playMetronome(time, playing) {
if (playing) {
osc = audioContext.createOscillator();
osc.connect(metronomeVolume);
metronomeVolume.connect(audioContext.destination);
osc.frequency.value = 500;
if (counter === 1) {
osc.frequency.value = 500;
} else {
osc.frequency.value = 300;
}
osc.start(time);
osc.stop(time + 0.1);
}
}
//______________________________________________END Metronome
function playTick() {
console.log("This is 16th note: " + counter);
counter += 1;
futureTickTime += counterTimeValue;
if (counter > 16) {
counter = 1;
}
}
function scheduler() {
if (futureTickTime < audioContext.currentTime + 0.1) {
playMetronome(futureTickTime , true);
playTick();
}
window.setTimeout(scheduler, 0);
}
scheduler();
//_____________________________________________BEGIN metronome
function playMetronome(time, playing) {
if (playing) {
osc = audioContext.createOscillator();
osc.connect(audioContext.destination);
osc.frequency.value = 500;
if (counter === 1) {
osc.frequency.value = 500;
} else {
osc.frequency.value = 300;
}
osc.start(time);
osc.stop(time + 0.1);
}
}
//______________________________________________END Metronome
function playTick() {
function scheduler() {
if (futureTickTime < audioContext.currentTime + 0.1) {
playMetronome(futureTickTime, true);
playTick();
}
window.setTimeout(scheduler, 0);
}
scheduler();
You might be wondering why the scheduleSound() function invocations are subtracting
the audioContext.currentTime from the futureTickTime().
scheduleSound(kickTrack, kick, counter, futureTickTime -
audioContext.currentTime);
This is done because the audio library you built in Chapter 13 is designed to reference
audioContext.currentTime by default and adds any additional numeric arguments to this
value. You subtract audioContext.currentTime from futureTickTime because these
values will be combined when the play() method of your library is invoked.
When scheduler() is invoked, the drum sequence does not start immediately because it
takes time for the audio buffers and files to load. This behavior can be corrected by modifying the
code so that the scheduler is initiated by a play/stop button. In your HTML code, create a button
with a class of play-stop-button and give it text of play/stop.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script src="https://ajax.googleapis.com/ajax/libs/
jquery/2.1.0/jquery.js"></script>
<script src="js/audiolib.js"></script>
<script src="js/app.js"></script>
<link rel="stylesheet" href="css/app.css">
</head>
<!--___________________________________________BEGIN APP-->
<body>
<!--HTML code-->
<button class="play-stop-button">
Play / Stop
</button>
</body>
<!--____________________________________________END APP-->
</html>
You now use JQuery to interface with the DOM and have to wrap your code in a document-
ready function. The following code defines the play/stop button functionality.
$(function() {
var futureTickTime = audioContext.currentTime,
counter = 1,
tempo = 120,
secondsPerBeat = 60 / tempo,
counterTimeValue = (secondsPerBeat / 4),
osc = audioContext.createOscillator(),
metronomeVolume = audioContext.createGain(),
timerID = undefined,
isPlaying = false;
/*_____________________________________________BEGIN load sound
samples*/
//_____________________________________________BEGIN metronome
if (playing) {
osc = audioContext.createOscillator();
osc.connect(metronomeVolume);
metronomeVolume.connect(audioContext.destination);
osc.frequency.value = 500;
if (counter === 1) {
osc.frequency.value = 500;
} else {
osc.frequency.value = 300;
}
osc.start(time);
osc.stop(time + 0.1);
}
}
//______________________________________________END Metronome
function playTick() {
console.log("This is 16th note: " + counter);
counter += 1;
futureTickTime += counterTimeValue;
if (counter > 16) {
counter = 1;
}
}
function scheduler() {
if (futureTickTime < audioContext.currentTime + 0.1) {
playMetronome(futureTickTime, true);
scheduleSound(kickTrack, kick, counter, futureTickTime -
audioContext.currentTime);
scheduleSound(snareTrack, snare, counter, futureTickTime -
audioContext.currentTime);
scheduleSound(hiHatTrack, hihat, counter, futureTickTime -
audioContext.currentTime);
scheduleSound(shakerTrack, shaker, counter, futureTickTime -
audioContext.currentTime);
playTick();
}
if (isPlaying) {
counter = 1;
futureTickTime = audioContext.currentTime;
scheduler();
} else {
window.clearTimeout(timerID);
}
}
$(".play-stop-button").on("click", function() {
play();
});
});
If you launch this code from your server and click the play/start button, it will start and stop
the application.
</head>
<!--_______________________________________________BEGIN APP-->
<body>
<div class="app-grid">
</div>
<button class="play-stop-button">
Play / Stop
</button>
<button class="metronome">Toggle metronome</button>
<div id="tempoBox">Tempo: <span id="showTempo">120</span>BPM
<input id="tempo" type="range" min="30.0" max="160.0"
step="1" value="120" ></div>
</body>
<!--_______________________________________________END APP-->
</html>
CSS
The following code is the CSS for the application.
body{
background-color:red;
font-size:25px;
}
button{
margin-bottom:5px;
font-size:25px;
}
.track-step{
width:50px;
height:50px;
display:inline-block;
background-color:orange;
outline-style:solid;
outline-width:1px;
margin-left:5px;
}
To create the div elements for the grid, you use a nested JavaScript for loop. Each
collection of grid items has a parent container.
function play() {
isPlaying = !isPlaying;
if (isPlaying) {
counter = 1;
futureTickTime = audioContext.currentTime;
scheduler();
} else {
window.clearTimeout(timerID);
}
}
//__________________________________BEGIN create grid
for (var i = 1; i <= 4; i += 1) {
$(".app-grid").append("<div class='track-" + i + "-container'
</div>");
for (var j = 1; j < 17; j += 1) {
$(“.track-” + i + “-container”).append(“<div class=’grid-item
track-step step-" + j + "'</div>");
}
}
//__________________________________END create grid
The following code allows you to toggle the metronome on and off.
$(".play-stop-button").on("click", function() {
play();
});
//_________________________________________BEGIN metronome toggle
$(".metronome").on("click", function() {
if (metronomeVolume.gain.value) {
metronomeVolume.gain.value = 0;
} else {
metronomeVolume.gain.value = 1;
}
});
//__________________________________________END metronome toggle
Next you write code that lets users control the tempo from the HTML input range slider and
displays the current tempo on the web page. First, modify the playTick() function:
function playTick() {
secondsPerBeat = 60 / tempo;
counterTimeValue = (secondsPerBeat / 4);
console.log("This is 16th note: " + counter);
counter += 1;
futureTickTime += counterTimeValue;
if (counter > 16) {
counter = 1;
}
}
Then create the event listener used to control the tempo from the slider:
$(".metronome").on("click", function() {
if (metronomeVolume.gain.value) {
metronomeVolume.gain.value = 0;
} else {
metronomeVolume.gain.value = 1;
}
});
$("#tempo").on("change", function() {
tempo = this.value;
$("#showTempo").html(tempo);
});
You can now modify the tempo of the sequence by moving the HTML input slider.
JQuery has a method named index() that allows you to capture an element’s index value
relative to a parent element. In the case of the sequencer application, the index value of the first
grid-item of each row is 0 and the last grid-item index is 15. You can give this value an
offset of +1 so that the first index grid-item is referenced as 1 and the last is referenced as 16.
This allows for a correlation between the grid-item index values and the counter value. You
can capture this information by setting an event listener to all elements with a class of grid-
item. When the user clicks the grid-item, the offset index value is either pushed to or
removed from a corresponding track array dependent on whether the grid-item is active or
not. This is what determines if a sound will play at a certain point in the music sequence. The
following code implements this feature and also modifies the CSS background-color of the
grid-item based on whether it is active or not.
//__________________________________BEGIN create grid
for (var i = 1; i <= 4; i += 1) {
$(".app-grid").append("<div class='track-" + i + "-container'</div>");
for (var j = 1; j < 17; j += 1) {
$(".track-" + i + "-container").append("<div class='grid-item
track-step step-" + j + "'</div>");
}
}
//__________________________________END create grid
}
});
}
sequenceGridToggler(".track-1-container", kickTrack);
sequenceGridToggler(".track-2-container", snareTrack);
sequenceGridToggler(".track-3-container", hiHatTrack);
sequenceGridToggler(".track-4-container", shakerTrack);
//______________________END Grid interactivity
var kickTrack = [ ],
snareTrack = [ ],
hiHatTrack = [ ],
shakerTrack = [ ];
You can now run the sequencer and play back sounds by clicking the squares. The tempo also
changes when the slider is moved.
Summary
In this chapter, you learned how to build a basic music sequencer. You now understand the core
techniques needed to build Web Audio API applications that rely on event scheduling.
24 AJAX and JSON
In this chapter, you are going to learn how to query data using web APIs and to create your own
web API for accessing synth patch data to use in a web audio synthesizer. Third-party web
services commonly allow a portion of their data to be accessible via a web API. This gives you
the ability to query data on their server and use their data in your applications. An example is the
iTunes public search API that lets developers search media titles in the iTunes store. To begin,
you must first learn about two technologies: AJAX and JSON.
AJAX
AJAX is an acronym that stands for Asynchronous JavaScript and XML. This is a technology that
allows you to use JavaScript to access data asynchronously. You have already worked with
AJAX in previous chapters when loading audio buffers using the XMLHttpRequest object.
The X in AJAX refers to XML, which stands for Extensible Markup Language. This was
originally the data exchange format used with AJAX and is rarely used now. In modern web
development, the data exchange format you use is JSON.
JSON
JSON stands for JavaScript Object Notation, and it is a data exchange format for transmitting
and receiving data over the HTTP protocol when working with web APIs. JSON objects are
nearly identical to JavaScript object literals, making them easy to work with. The difference
between a JSON object and a JavaScript object literal is that JSON objects are not assigned to a
variable and their keys need to be written as strings. JSON objects are stored in JavaScript files.
The following code is an example of a JSON object.
{
"buzzFunk": [{
"type": "sawtooth",
"frequency": 65.25
}, {
"type": "triangle",
"frequency": 65.25
}, {
"type": "sawtooth",
"frequency": 67.25
}]
}
});
});
Go to Start Sublime Server and in your web browser go to localhost:8080. Open the console
and you will see an object being returned.
If you click the arrow and unfold the object, you will see a list of objects that each contains
data.
You have just queried the iTunes search API for any music that includes the keyword “funk”
and are now in possession of a JavaScript object that contains this data.
The part of the endpoint after the “?” symbol is called the query string. This part of the URL
contains the data that is being queried. The “&” symbol separates the key/value pairs.
The iTunes API search terms are assigned to specific keys and in the previous code, these are
term and media. There is no standardization across web APIs for key/value names, and they
are different for each web API. Because the URL structure for all web APIs is different, you will
need to read the documentation for any that you are working with. The documentation for the
iTunes search API is here: https://affiliate.itunes.apple.com/resources/documentation/itunes-
store-web-service-search-api/.
The next part of the URL string lets you to set a callback to run once the query completes. In the
code example, the callback of $.getJSON is used. If you want to make a call to the iTunes
search API on page load and invoke a function on completion, it looks like the following code:
HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>app</title>
<script type=”text/javascript” src=”https://ajax.googleapis.
com/ajax/libs/jquery/2.1.0/jquery.js”>
</script>
<script src=”js/app.js”></script>
<link rel=”stylesheet” href=”css/app.css”>
</head>
<!--_______________________________________________BEGIN APP-->
<body>
</body>
<!--_______________________________________________END APP-->
</html>
JavaScript
function logger(data) {
console.log(data);
}
In the previous example, a function named logger is run when the query completes.
The $.getJSON method takes a callback as a second argument. The callback returns the data
object via an argument. In the following code, this argument is named data, but you can name it
anything you want.
$.getJSON(apiURL, function(data) {
console.log(data);
});
}, {
"type": "triangle",
"frequency": 65.25
}, {
"type": "sawtooth",
"frequency": 67.25
}]
}
Go to Start Sublime Server and in your web browser go to localhost:8080. In the Chrome
console, you will see the JSON object.
If you unfold the object, you will see its internals.
A JSON object is different from a regular JavaScript object literal because it is not assigned to
a variable. However, once the data is returned, it is assigned to a variable and can be passed
around and assigned to other variables.
$(function() {
$.getJSON(“js/data.js”, function(data) {
var patchParams = data;
console.log(patchParams); // object
});
});
The Data Structure
The data structure of the JSON object you are working with contains a single object property
named buzzFunk, which is an array containing three objects and each holds oscillator data.
When this data is loaded into your synth, all three oscillators combine to create a single sound.
Each object has type and frequency settings for the oscillator that references it.
The HTML and CSS codes for the keyboard interface used to play the synth that loads the
JSON data are given below. Copy the HTML code to index.html and the CSS code to
app.css.
HTML
<body>
<h1> Synthy API </h1>
<ul id="piano">
CSS
body{
background-color:purple;
}
h1{
font-family:"impact";
color:rgb(228, 208, 230);
margin-left:10%;
font-size:70px;
}
li {
list-style:none;
float:left;
display:inline;
width:40px;
position:relative;
}
.white-key{
display:block;
height:220px;
background:#fff;
border:1px solid #ddd;
border-radius:0 0 3px 3px;
}
.black-key {
display:inline-block;
position:absolute;
top:0px;
left:-12px;
width:25px;
height:125px;
background:#000;
z-index:1;
The application uses a factory function to load the JSON data. This function takes two
arguments. The first argument is an endpoint that contains the JSON file, and the second is a
property of the JSON object that contains the patch you want to load. Currently, the JSON file has
only one patch named buzzFunk. The final loading interface for the JSON data looks like the
following code:
var synth = apiReader("js/data.js", "buzzFunk"); // load patch
synth.play(keyByDOMIndex); // play a specific note on keyboard
synth.stop(); // stop playing
Delete any code present in app.js and replace it with the following code:
"use strict";
var synth = apiReader("js/data.js", "buzzFunk");
$(function() {
$(".key").on("mouseover", function() {
var index = $(this).index('.key');
synth.play(index);
});
$(".key").on("mouseout", function() {
synth.stop();
});
});
In the “js” folder, create a new file named module.js and reference it in the index.html
file between the JQuery library and app.js file.
<head>
<meta charset="UTF-8">
<title>app</title>
<script type="text/javascript" src="https://ajax.googleapis.
com/ajax/libs/jquery/2.1.0/jquery.js" charset="utf-8"></script>
<script src="js/module.js"></script>
<script src="js/app.js"></script>
<link rel="stylesheet" href="css/app.css">
</head>
$(function() {
$.getJSON(endpoint, function(data) {
app.patchParams = data[patchProp];
})
});
var app = {
patchParams: undefined,
oscillators: undefined,
play: function(id) {
app.oscillators = app.patchParams.map(function(val) {
return osc;
});
},
stop: function() {
for (var i = 0; i < app.oscillators.length; i += 1) {
app.oscillators[i].stop(audioContext.currentTime);
}
}
}
return app;
};
Launch the index.html file from Sublime Server and hover your mouse over the synth keys.
You will hear the synth play a collection of oscillators that reference the settings in the loaded
patch data.
The following code provides the index value of a DOM element (the keyboard note the user
hovers their mouse over) multiplied by 100. The result is added to the oscillator frequency and
assigned to the detune.value property. This makes the oscillators play back at half-step
intervals relative to the keyboard interface.
osc.detune.value = (val.frequency) + (id * 100);
Each oscillator is then returned and stored in an array named app.oscillators.
The stop method is used to stop the oscillators from playing. This method loops through
app.oscillators and invokes a Web Audio API stop method on each one.
stop: function() {
for (var i = 0; i < app.oscillators.length; i += 1) {
app.oscillators[i].stop(audioContext.currentTime);
}
}
In app.js, the apiReader function is invoked, which returns an object named synth.
var synth = apiReader("js/data.js", "buzzFunk");
The play and stop methods are placed in two event listeners to start and stop the oscillators
on mouse events.
$(".key").on("mouseover", function() {
var index = $(this).index('.key');
synth.play(index);
});
$(".key").on("mouseout", function() {
synth.stop();
});
When the play method is invoked, the current index value of the div element (the “keyboard
note”) is captured and passed to the function.
var index = $(this).index('.key');//__get index value of key
synth.play(index);//__________________pass it to play method
data.js
{
"buzzFunk": [{
"type": "sawtooth",
"frequency": 65.25,
"volume": 1
}, {
"type": "triangle",
"frequency": 65.25,
"volume": 1
}, {
"type": "sawtooth",
"frequency": 67.25,
"volume": 0.3
}]
}
module.js
"use strict";
var audioContext = new AudioContext();
var apiReader = function(endpoint, patchProp) {
$(function() {
$.getJSON(endpoint, function(data) {
app.patchParams = data[patchProp];
})
});
var app = {
patchParams: undefined,
gainNodes: undefined,
oscillators: undefined,
play: function(id) {
app.gainNodes = app.patchParams.map(function(val) {
});
app.oscillators = app.patchParams.map(function(val, i) {
return osc;
});
},
stop: function() {
for (var i = 0; i < app.oscillators.length; i += 1) {
app.oscillators[i].stop(audioContext.currentTime);
}
}
}
return app
};
These file modifications give your code the ability to create a gain node for each oscillator.
The play method of the app object contains a map method that creates the gain nodes and sets
the gain.gain.value property of each one to the value of the current object’s volume
property. All gain nodes are placed in an array that is assigned to app.gainNodes.
app.gainNodes = app.patchParams.map(function(val) {
var gain = audioContext.createGain();
gain.gain.value = val.volume;
return gain;
});
The oscillators are then connected to the gain nodes in the second map method.
app.oscillators = app.patchParams.map(function(val, i) {
return osc;
});
"type": "sawtooth",
"frequency": 67.25,
"volume": 0.3
}],
"gameSound": [{
"type": "square",
"frequency": 100.25,
"volume": 1
}, {
"type": "triangle",
"frequency": 65.25,
"volume": 1
}, {
"type": "sawtooth",
"frequency": 67.25,
"volume": 0.3
}]
}
You then access the gameSound settings by loading them with apiReader.
var synth = apiReader("js/data.js", "gameSound");
Summary
In this chapter, you learned how to query third-party web APIs, work with JSON files, and create
your own web API to load patch data for a synthesizer. The application you created only begins to
explore what is possible. For a challenge, try incorporating filters, LFOs, delays, and other
settings. In the next chapter, you will learn about the future of JavaScript and various resources
for continued learning.
25 The Future of JavaScript and the Web
Audio API
In this book, you have learned the core concepts behind the JavaScript programming language and
the Web Audio API. To keep from overcomplicating things, some parts of both the JavaScript
language and the Web Audio API have been omitted. This chapter presents some of the areas that
were skipped and provides you a few suggestions about what you can learn now to future-proof
your new skills.
3D Spacial Positioning
In addition to the StereoPanner node, there are two other spacial positioning nodes. Both of
these nodes allow for 3D style panning. One is called Panner and the other is called
SpacialPanner. Panner has been recently deprecated. The replacement for Panner is
SpacialPanner. As of this writing, SpacialPanner has not been implemented in any web
browsers. This makes it difficult to write about it and check the accuracy of code samples. And
for this reason, we opted to omit a detailed explanation of SpacialPanner and present a
general summary here.
The idea behind 3D spacial positioning is that sound is modified in relation to two objects in a
three-dimensional space. The first object is a sound source that has its spacial positioning moved
using SpatialPanner. The other object is called SpatialListener that represents a
real-world human listener. The utility of this approach is that the SpatialListener object
can be programmed to work with an avatar such as a video game character, where sound that is
generated in a “virtual world” is perceived from a first-person perspective. Volume changes take
place automatically based on the virtual “distance” between the SpatialListener and any
sound-generating virtual objects. For added realism, filters, reverberation, and other effects can
be programmed to change the characteristics of sound based on the perceived position of virtual
objects. You can read about SpatialPanner at the following URL:
https://www.w3.org/TR/webaudio/#the-spatialpannernode-interface.
You can read about the SpatialListener at this URL:
https://www.w3.org/TR/webaudio/#idl-def-SpatialListener.
node.js
https://nodejs.org
Node.js is a server-side JavaScript environment based on V8, which is the same JavaScript
engine that runs Google Chrome. Instead of running JavaScript from a web browser, Node.js
allows you to run JavaScript from the terminal on your computer. It can be used to automate
computer tasks, run web servers, and communicate with databases.
Summary
In this chapter, you were presented with a list of options for continued learning. Even though
JavaScript has been taught here in the context of working with audio, it is important to keep in
mind that programming is a useful cross-disciplinary skill that you can use to solve many different
types of problems.
Further Reading
■ JavaScript: The Definitive Guide by David Flanagan.
■ Understanding ECMAScript 6 by Nicholas C. Zakas.
■ Programming JavaScript Applications: Robust Web Architecture with Node, HTML5, and
Modern JS Libraries by Eric Elliott.
■ You Don’t Know JS Book Series by Kyle Simpson.
■ Node.js the Right Way: Practical, Server-Side JavaScript That Scales by Jim R. Wilson.
■ Web Audio API by Boris Smus.
Book Website
http://www.javascriptforsoundartists.com
Index
A
Abstraction, 129–135, 185–189
Addition assignment, 24–25
AJAX, 3, 207–209
Algebra rules, 18
Ambience, 151
Analyser node, 175–183
AND operator, 29
Animation, 107, 182
Anonymous functions, 48–49; see also Functions
API (application programming interface), 3–4; see also Web Audio API
App interface, modifying, 81–84
Application, building, 71–89, 93–102
Application, refactoring, 108–110
Arguments, 16, 43–44
Arithmetic operators, 18, 23–25; see also Numbers
Arrays
callback functions and, 53–54
for loops and, 36–37
frequency data arrays, 180–182
numbers and, 21–22
objects and, 60
track arrays, 197–206
Assignment operators, 11, 23–25; see also Operators
Asynchronous code execution, 118–120
Asynchronous JavaScript and XML (AJAX), 3, 207–209
Attack property, 166
AudioBatchLoader function, 130–131, 134–135, 185–189
AudioContext() method, 65
AudioFileLoader function, 131–134, 186–192, 198–200
Audio files
abstracting, 129–135
asynchronous code execution, 118–120
buffer, 116–120, 222
compatibility issues, 118
decoding, 116, 119
get requests, 116–120
importing, 117–120
impulse response files, 151–154
loading, 115–120, 129–135, 185–192, 198–200
modifying, 222
playing, 10, 115–120, 129–135
prerequisites for, 115–116
synchronous code execution, 118–120
XMLHttpRequest, 116–120
Audio input sources, 137–142, 155–167, 175–179, 183–185
Audio loader abstraction, 185–189
Audio loader library
creating, 121, 185
flexibility for, 185–189
modifying, 186–189
updating, 171, 183, 185
Audio parameters, 140–141, 171–174
Audio visualizations, creating, 175–183
B
Background color, 78–82, 86–87, 178, 203–206, 214
Bang operator, 28
Binary-coded decimal numbers, 176
Binary system, 176
Bin count, 177–182
Bind function, 62–64
Bins, 179–182
Biquad filter node
for audio filtering, 140–142
for designing equalizers, 144–149
types of, 144–147
using, 143–144
Bits, 176
Block-level element, 75–76, 84, 87–89
Boolean data type, 25–26
Boolean values, 25–29
Border, 84–85
Bound object, 62–64
Box model, 74
Browser setup view, 6
Buffer data, modifying, 222
Buffer, processing, 116–120
Buffer source node, 119–120
Bullet points, 86
Bytes, 176
C
Callback functions, 52–54
Case insensitivity, 16
CDN (content delivery network), 103–105
Centering elements, 87–89
Changeability, 13
Channel merger nodes, 159–160
Channel merging, 158–160
Channel splitting, 158–160
CharAt(), 15–17
Child selectors, 80
Chrome developer tools, 7–8, 10, 14, 78
Classes, 121–122
Class identifier, 81
Cloning objects, 60
Closures, 49–52
Code, 10–12
Code abstraction, 129–135
Code block, 36, 79
Code snippets, 6–7
Color selection, 78–82, 86–87, 178, 203–206, 214
Comments, 12, 72, 79
Comparison operators, 26–30; see also Operators
Compatibility issues, 118
Concat(), 21–22
Concatenation, 14–15, 20–22
Conditional statements, 31–35
Console.log(), 13–14
Constructors, 121, 125–128
Convolver node, 140, 151–155
Convolver reverb, 151–155
CSS (cascading style sheets)
app interface, 81–84
block-level elements, 87–89
border, 84–85
for building user interface, 77–89
bullet points, 86
buttons, 74–79
checking errors in, 77–78
child selectors, 80
code block, 79
color selection, 78–82, 86–87, 178, 203–206, 214
comments, 79
data structure of, 214–216
descendent selectors, 80
DOM programming, 91–93, 102
element selectors, 79
explanation of, 2–3, 74, 77–78
inline element, 84
with JQuery, 106–108
link element, 77
list element, 86
margins, 84–85, 87–89
method chaining, 107–108
padding, 84–85
selecting elements, 105
sequencer, 203–204
sliders, 74, 76–78
spectrum analyzer, 178
D
Data.js, 211–217, 220
Data, private, 124–125, 127
Data, query, 207–220
Data types, 11, 57–58
Decimal numbers, 19, 169, 176, 180
Delay node, 140, 161–164
Delete keyword, 188
Descendent selectors, 80
Destination, 9–10
Detune property, 69
Developer tools, 7–8, 10, 14, 78
Display interface, building, 181–182
Div element, 73, 75–76
Division assignment, 25
Document Object Model (DOM), 74
DOM programming
API methods, 92
building application, 93–102
CSS and, 91–93, 102
event listener, 99–100
explanation of, 74
frequency slider, 96–99
frequency spectrum analyzer, 181–183
HTML and, 74, 91–102
with JavaScript, 74, 91–102, 181
with JQuery, 103–113
nodes, 96, 100
selectors, 105–106, 111–112, 153
setInterval method, 97–101
simplifying, 103–113
start/stop text, 94–96
triggering oscillator, 93–94
Drum sequence, 191–206; see also Sequencer
Dynamic compressor node, 165–167
Dynamic object extension, 123–124; see also Objects
Dynamic range compression, 140, 165–167
E
Echo effects, 162
Effects
building blocks for, 141–142
echo effects, 162
effects box, 42–43
example of, 141
nodes for, 139–142
ping-pong effects, 163–164
slap back effects, 162–163
types of, 139–142
working with, 137–142
Elements
block-level elements, 75–76, 84, 87–89
centering, 87–89
changing, 106–107
div element, 73, 75–76
form element, 73–74, 76–78
heading element, 73
horizontal rule element, 73, 76
inline element, 75–76, 84
input element, 73–74, 76–78, 107–108, 110
link element, 77
list element, 73, 86
list item element, 73, 86
paragraph element, 73
parent element, 80, 86–87, 205
selecting, 79, 105
span element, 73, 75–76
storing, 105–106
unordered list element, 73, 81
Element selectors, 79, 105
Equality operator, 26–27
Equalizers, 137, 140, 142–149
Equal to operator, 27–28
Event listener
audio buffer, 120
DOM programming, 99–100
JQuery, 107–111
playback, 135, 217
tempo changes, 204–205
ExponentialRampToValueAtTime method, 172
F
Factories, 121–128
Fast Fourier transforms (FFTs), 176, 179–182
File loader, 115–120, 129–135
File type compatibility, 118
Filter(), 53
Filter types, 144–147
Font color, 86–87
Font size, 86–87
Font type, 86–87
For in loop, 59–60
For loop, 35–37
Form element, 73–74, 76–78
Fourier analysis, 175–176
Frequency, changing, 97–99
FrequencyData array, 180–182
Frequency data, storing, 180–181
Frequency property, 69
Frequency slider, 96–99
Frequency spectrum analyzer, 176–183
Functions
anonymous functions, 48–49
arguments object, 43–44
bind function, 62–64
callback functions, 52–54
closures, 49–52
declaring variables, 46–50
effects box, 42–43
example of, 39–44
explanation of, 39
expressions for, 41
hoisting, 46–48
oscillator playback, 41–43
parts of, 40–41
recursion, 54–55
scope concept, 44–47
G
Gain nodes, 138–139, 194
Get requests, 116–120
Getters, 124–125
Global object, 63–64
Global replace, 16
Global scope, 44–47, 64
Global variables, 46–50, 119
Google Chrome, 4–7; see also Chrome developer tools
Graphic equalizer, 143, 146–148; see also Equalizers
Greater than operator, 27–28
Grouping selectors, 80
H
Heading element, 73
“Hello Sound” application, 9–11
Hoisting variables, 46–48
Horizontal rule element, 73, 76
HTML (hypertext markup language)
block-level elements, 75–76
for building user interface, 71–77
checking errors in, 72
comments, 72
data structure, 213
DOM programming, 74, 91–102
elements, 2, 71–74
explanation of, 2–3, 71–74
form element, 76–78
impulse response files, 153
inline element, 75–76
input element, 76–78
iTunes API, 210
JQuery and, 105–108
method chaining, 107–108
selecting elements, 105
sequencer, 202–203
spectrum analyzer, 177–178
tags, 2, 71–74
templates, 72–73
this keyword, 108
tree structure, 74–75
type attribute, 76–77, 107
value attribute, 77
I
Identifiers, 81
id identifier, 81
If statement, 32–33
Immutability, 13
Impulse response files, 151–154
Inline element, 75–76, 84
In operator, 60
Input element, 73–74, 76–78, 107–108, 110
Input sources, 137–142, 155–167, 175–179, 183–185
iTunes API, 208–210
J
JavaScript
building application, 93–102
changing properties, 106–107
classes and, 121–122
data types, 11, 57–58
DOM programming, 74, 91–102, 181
explanation of, 1–2
future of, 221–223
getting started with, 9–22
impulse response files, 151–154
method chaining, 107–108
new keyword, 117, 125–126
object-oriented programming, 121–122
overview of, 1–8
setup view in browser, 4–6
spectrum analyzer, 176–177
strict mode for, 5, 63–64, 93
this keyword, 61–62, 108, 126
JavaScript 6, 222
JavaScript Object Notation, 208; see also JSON object
JQuery
CDN and, 103–105
changing properties, 106–107
CSS and, 106–109
DOM programming, 103–113
event listener, 107–111
explanation of, 103
HTML and, 105–108
method chaining, 107–108
methods, 106
onOff in, 112
oscillator with, 108–109
refactoring application, 108–110
referencing, 104
selecting elements, 105
setInterval in, 111–112
setup, 103–104
spectrum analyzer, 176–177
storing elements, 105–106
this keyword, 108
using, 105–113
JSON object
data.js, 211–217, 220
data structure, 213–216
explanation of, 207–208
extending, 219–220
iTunes API, 208–210
module.js, 215–219
patches, 210–212, 216, 219–220
playback, 216–217
for web API, 210–217
K
Knee property, 166
L
Length property, 15, 17
Less than operator, 27–28
LinearRampToValueAtTime method, 173
Link element, 77
List bullet points, 86
List element, 73, 86
List item element, 73, 86
Local scope, 44–47
Logical AND operator, 29
Logical NOT operator, 29–30
Logical operators, 28–30
Logical OR operator, 29
Loops
explanation of, 31–32
for loop, 35–37
for in loop, 59–60
looping sounds, 170–171
loop property, 170–171
while loop, 37–38
Low-pass filter, 141
M
Mac setup view, 6
Map(), 53–54
Margins, 84–85, 87–89
Math.abs(), 20
Math.ceil(), 19
Math.floor(), 19
Math.max(), 19
Math methods, 18–20
Math.min(), 19
Math object, 18
Math.random(), 19–20
Merger nodes, 155, 158–160
Method chaining, 107–108
Metronome function, 194–205
Mixing channels, 138
Modification nodes, 139
Module.js, 215–219
Modulo assignment, 25
Mono channels, 159–160
Multiband equalizer, 140, 142; see also Equalizers
Multichannel files, 157–160
Multiplication assignment, 25
Music sequencer, 191–206; see also Sequencer
Mutability, 13
N
New keyword, 117, 125–126
Node graphs; see also Nodes
buffer node source, 119–120
effects nodes, 139–142
example of, 141
explanation of, 137–138
gain nodes, 138–139
input sources, 138
modification nodes, 139
for Web Audio API, 65–66, 137–142
Node.js, 223
Nodes
analyser node, 175–183
biquad filter node, 140–149
buffer source node, 119–120
channel merger nodes, 159–160
channel splitting, 158–160
convolver node, 140, 151–155
delay node, 140, 161–164
dynamic compressor node, 140, 165–167
effects nodes, 139–142
explanation of, 138–139
future of, 221–223
gain nodes, 138–139, 194
merger nodes, 155, 158–160
modification nodes, 139
node graphs, 65–66, 137–142
placement of, 138–139
splitter nodes, 158–160
stereo panner node, 140, 157–158
for Web Audio API, 65–66, 137–142
Not equal to operator, 28
NOT operator, 29–30
Null, 12
Numbers
algebra rules, 18
arithmetic operators, 18, 23–25
arrays and, 21–22
decimals, 19, 169, 176, 180
math methods, 18–20
math object, 18
precedent example, 18–20
precedent rules, 18
Number-to-string conversion, 20
O
Object literals, 57–58, 117, 208, 212
Object-oriented programming, 121–122
Objects
arrays and, 60
bound object, 62–64
classes and, 122
cloning, 60–61
data types, 57–64
dynamic object extension, 123–124
factories and, 122–124
global object, 63–64
literals, 57–58, 117, 208, 212
looping through, 59–60
method for, 60
programming, 121–122
property for, 60
prototypal inheritance, 61–62
prototype object, 126–128
Onended property, 67
OnOff selector, 93–101, 112
Open Sound Control (OSC), 223
Operand, 23
Operators
addition assignment, 24–25
arithmetic operators, 18, 23–25
assignment operators, 11, 23–25
bang operator, 28
categories of, 23–25
comparison operators, 26–30
division assignment, 25
equality operator, 26–27
equal to operator, 27–28
explanation of, 23–24
greater than operator, 27–28
in operator, 60
less than operator, 27–28
logical operators, 28–30
modulo assignment, 25
multiplication assignment, 25
not equal to operator, 28
operand, 23
setInterval method, 24–25
strict equality operator, 27
strict not equal to operator, 28
subtraction assignment, 25
OR operator, 29
Oscillators
creating, 41–45, 66–69, 119–120
detune property, 69
frequency property, 69
with JQuery, 108–109
methods for, 66–67
onended property, 67
playback, 9–10, 32, 41–43, 66–67, 93–94, 108–109
properties of, 66–67
refactoring application, 108–110
restarting, 67–68
start/stop text, 94–96
stop method, 67–68
triggering, 93–94
type property, 68–69
for variables, 12–13
for Web Audio API, 9–10, 66–69
P
Padding, 84–85
Paragraph element, 73
Parametric equalizers, 143, 146–149
Parent element, 80, 86–87, 205
Ping-pong delay, 163–164
Playback
audio files, 10, 115–120, 129–135
audio parameters, 171–174
convolver reverb, 153–154
event listener, 135, 217
explanation of, 4
for JSON object, 216–217
looping sounds, 170–171
oscillator playback, 9–10, 32, 41–43, 66–67, 93–94, 108–109
for sequencer, 192, 197–202
Pop(), 21–22
Precedent example, 18–20
Primitive data types, 57
Private data, 124–125, 127
Property
attack property, 166
changing, 106–107
detune property, 69
explanation of, 17
frequency property, 69
knee property, 166
length property, 15, 17
loop property, 170–171
onended property, 67
prototype property, 126–128
ratio property, 166
reduction property, 165–167
release property, 166
threshold property, 166
type property, 68–69
Prototypal inheritance, 61–62
Prototype object, 126–128
Prototype property, 126–128
Push(), 21
Q
Query string, 209–210
R
Ratio property, 166
Recursion, 54–55
Reduction property, 165–167
Regular expressions, 16
Release property, 166
Replace(), 16
Resources, 8, 15, 151–152, 222–223
Reverb, 151–155
S
Scope concept, 44–47
Selectors
child selectors, 80
descendent selectors, 80
DOM selectors, 105–106, 111–112, 153
element selectors, 79, 105
grouping, 80
storing, 105–106
Sequencer
building, 191–206
CSS for, 203–204
grid elements, 202–206
HTML for, 202–203
interactivity for, 205–206
metronome, 194–205
playback, 192, 197–202
rhythmic patterns, 192–193
tempo changes, 192, 195–206
timing clock, 193–204
track arrays, 197–206
user interface grid, 202–206
SetInterval method
assignment operators, 24–25
DOM programming, 97–101
JQuery, 111–112
rhythmic patterns, 192–193
SetTargetAtTime() method, 173
Setters, 124–125
SetTimeout method, 192–197, 199–201
SetValueAtTime method, 172
SetValueCurveAtTime() method, 173–174
Shift(), 21–22
Single-band equalizer, 143, 148; see also Equalizers
Single mono channels, 159–160
Slap back effects, 162–163
Slice(), 16–17
Spacial planner node, 221–222
Span element, 73, 75–76
Spatial listener node, 221–222
Speakers, 138
Spectrum analyzer, connecting, 181–183
Spectrum analyzer, creating, 176–183
Splitter nodes, 158–160
Start method, 170–171
Step sequencer, 191–206; see also Sequencer
Stereo panner node, 140, 157–158, 221–222
Stop method, 67–68
Strict equality operator, 27
Strict mode, 5, 63–64, 93
Strict not equal to operator, 28
Strict string, 93–97, 100, 109
String data types, 11, 14–16
String methods, 15–17
Strings
case insensitivity, 16
data types, 11, 14–16
global replace, 16
length property, 15, 17
manipulating, 14–17
methods, 15–17
number-to-string conversion, 20
regular expressions, 16
resources for, 15
values for, 16–17
variables and, 14–17
Sublime Text, 4–6, 115
Subtraction assignment, 25
Switch statement, 33–34
Synchronous code execution, 118–120
Synthesizer patch data, 210–212, 216, 219–220
T
Template folders, 4–5
Templates, 72–73
Tempo, changing, 192, 195–206
Ternary statement, 34–35
Text size, 86–87
Third-party web APIs, 207–220
This keyword, 61–62, 108, 126
Threshold property, 166
Time
audio parameter methods, 171–174
looping sounds, 170–171
start method, 170–171
timing clock, 169–170, 193–204
working with, 169–174
ToLowerCase(), 15
ToUpperCase(), 15
Track arrays, 197–206
Troubleshooting tips, 8–9
Type attribute, 76–77
Type property, 68–69
Typestyle, 86–87
U
Unordered list element, 73, 81
Unshift(), 21–22
User interface (UI)
building, 71–89
CSS for, 77–89
explanation of, 71–72
HTML for, 71–77
modifying, 81–84
V
Value attribute, 77, 106, 108, 110
Values
assigning, 11, 13, 23–26
Boolean values, 25–29
for strings, 16–17
Variables
assigning values to, 11, 13, 23–26
assignments, 10–14
changeability of, 13
concatenation of, 14–15
creating, 10
data type of, 17–18
declaring, 46–50
global variables, 46–50, 119
hoisting, 46–48
immutability of, 13
mutability of, 13
names, 13–14
null value, 12
oscillator for, 12–13
overwriting, 13
strings and, 14–17
undefined value, 12
understanding, 10–11
waveforms, 10–14, 17–18, 21
Visualizations, creating, 175–183
Volume control, 138
W
W3C (World Wide Web Consortium), 72, 77
Waveforms
changing, 99–100
outline for, 101–102
selecting, 99–102
types of, 10–14, 41–44, 68–69, 99–102
variables, 10–14, 17–18, 21
Web Audio API
audio output, 9–10
creating, 210–212
destination, 9–10
explanation of, 3–4
features of, 65–66
future of, 221–223
getting started with, 9–22
“Hello Sound” application, 9–11
nodes for, 65–66, 137–142
oscillators, 9–10, 66–69
resources for, 8
variables, 10–11
waveforms, 11–14, 17–18, 21
Web MIDI API, 223
While loop, 37–38
Windows setup view, 6
Work environment, 4–6
X
XML (Extensible Markup Language), 207; see also AJAX
XMLHttpRequest, 116–120, 131–133, 152–153, 207–209
Z
Zero-based index, 16, 43