var audio = new AudioContext();
Create the object that contains functions that use web audio to make sound.
var audio = new AudioContext();
Create the data for the drum machine.
var data = {
step
represents the current step (or beat) of the
loop.
step: 0,
tracks
holds the six tracks of the drum machine. Each
track has a sound and sixteen steps (or beats).
tracks: [createTrack("gold", note(audio, 880)),
createTrack("gold", note(audio, 659)),
createTrack("gold", note(audio, 587)),
createTrack("gold", note(audio, 523)),
createTrack("gold", note(audio, 440)),
createTrack("dodgerblue", kick(audio))]
};
Runs every hundred milliseconds.
setInterval(function() {
Increase data.step
by one. If
data.step
is 15
(the last step) loop
back around to 0
(the first step).
data.step = (data.step + 1) % data.tracks[0].steps.length;
Find all the tracks where the current step is on. Play the sounds for those tracks.
data.tracks
.filter(function(track) { return track.steps[data.step]; })
.forEach(function(track) { track.playSound(); });
}, 100);
Get the screen
object. This is a bundle of functions
that draw in the canvas element.
var screen = document.getElementById("screen").getContext("2d");
draw() draws the drum machine. Called once at the
beginning of the program. It’s then called 60 times a second
forever (see the call to
requestAnimationFrame()
below).
(function draw() {
Clear away the previous drawing.
screen.clearRect(0, 0, screen.canvas.width, screen.canvas.height);
Draw all the tracks.
drawTracks(screen, data);
Draw the pink square that indicates the current step (beat).
drawButton(screen, data.step, data.tracks.length, "deeppink");
Ask the browser to call draw()
again in the near
future.
requestAnimationFrame(draw);
})();
setupButtonClicking() sets up the event handler that will make mouse clicks turn track buttons on and off.
(function setupButtonClicking() {
Every time the user clicks…
addEventListener("click", function(e) {
…Get the coordinates of the mouse pointer relative to the canvas…
var p = { x: e.offsetX, y: e.offsetY };
…Go through every track…
data.tracks.forEach(function(track, row) {
…Go through every button in this track…
track.steps.forEach(function(on, column) {
…If the mouse pointer was inside this button…
if (isPointInButton(p, column, row)) {
…Switch it off if it was on or on if it was off.
track.steps[column] = !on;
}
});
});
});
})();
note() plays a note with a pitch of
frequency
for 1
second.
function note(audio, frequency) {
return function() {
var duration = 1;
Create the basic note as a sine wave. A sine wave produces a pure
tone. Set it to play for duration
seconds.
var sineWave = createSineWave(audio, duration);
Set the note’s frequency to frequency
. A greater
frequency produces a higher note.
sineWave.frequency.value = frequency;
Web audio works by connecting nodes together in chains. The output of one node becomes the input to the next. In this way, sound is created and modified.
chain([
sineWave
outputs a pure tone.
sineWave,
An amplifier reduces the volume of the tone from 20% to 0 over the duration of the tone. This produces an echoey effect.
createAmplifier(audio, 0.2, duration),
The amplified output is sent to the browser to be played aloud.
audio.destination]);
};
};
kick() plays a kick drum sound for
1
second.
function kick(audio) {
return function() {
var duration = 2;
Create the basic note as a sine wave. A sine wave produces a pure
tone. Set it to play for duration
seconds.
var sineWave = createSineWave(audio, duration);
Set the initial frequency of the drum at a low 160
.
Reduce it to 0 over the duration of the sound. This produces that
BBBBBBBoooooo….. drop effect.
rampDown(audio, sineWave.frequency, 160, duration);
Web audio works by connecting nodes together in chains. The output of one node becomes the input to the next. In this way, sound is created and modified.
chain([
sineWave
outputs a pure tone.
sineWave,
An amplifier reduces the volume of the tone from 40% to 0 over the duration of the tone. This produces an echoey effect.
createAmplifier(audio, 0.4, duration),
The amplified output is sent to the browser to be played aloud.
audio.destination]);
};
};
createSineWave() returns a sound node that plays
a sine wave for duration
seconds.
function createSineWave(audio, duration) {
Create an oscillating sound wave.
var oscillator = audio.createOscillator();
Make the oscillator a sine wave. Different types of wave produce different characters of sound. A sine wave produces a pure tone.
oscillator.type = "sine";
Start the sine wave playing right now.
oscillator.start(audio.currentTime);
Tell the sine wave to stop playing after
duration
seconds have passed.
oscillator.stop(audio.currentTime + duration);
Return the sine wave.
return oscillator;
};
rampDown() takes value
, sets it to
startValue
and reduces it to almost 0
in
duration
seconds. value
might be the
volume or frequency of a sound.
function rampDown(audio, value, startValue, duration) {
value.setValueAtTime(startValue, audio.currentTime);
value.exponentialRampToValueAtTime(0.01, audio.currentTime + duration);
};
createAmplifier() returns a sound node that
controls the volume of the sound entering it. The volume is
started at startValue
and ramped down in
duration
seconds to almost 0
.
function createAmplifier(audio, startValue, duration) {
var amplifier = audio.createGain();
rampDown(audio, amplifier.gain, startValue, duration);
return amplifier;
};
chain() connects an array of
soundNodes
into a chain. If there are three nodes in
soundNodes
, the output of the first will be the input
to the second, and the output of the second will be the input to
the third.
function chain(soundNodes) {
for (var i = 0; i < soundNodes.length - 1; i++) {
soundNodes[i].connect(soundNodes[i + 1]);
}
};
createTrack() returns an object that represents a
track. This track contains an array of 16 steps. Each of these are
either on (true
) or off (false
). It
contains color
, the color to draw buttons when they
are on. It contains playSound
, the function that
plays the sound of the track.
function createTrack(color, playSound) {
var steps = [];
for (var i = 0; i < 16; i++) {
steps.push(false);
}
return { steps: steps, color: color, playSound: playSound };
};
var BUTTON_SIZE = 26;
buttonPosition() returns the pixel coordinates of
the button at column
and row
.
function buttonPosition(column, row) {
return {
x: BUTTON_SIZE / 2 + column * BUTTON_SIZE * 1.5,
y: BUTTON_SIZE / 2 + row * BUTTON_SIZE * 1.5
};
};
drawButton() draws a button in
color
at column
and row
.
function drawButton(screen, column, row, color) {
var position = buttonPosition(column, row);
screen.fillStyle = color;
screen.fillRect(position.x, position.y, BUTTON_SIZE, BUTTON_SIZE);
};
drawTracks() draws the tracks in the drum machine.
function drawTracks(screen, data) {
data.tracks.forEach(function(track, row) {
track.steps.forEach(function(on, column) {
drawButton(screen,
column,
row,
on ? track.color : "lightgray");
});
});
};
isPointInButton() returns true if p
,
the coordinates of a mouse click, are inside the button at
column
and row
.
function isPointInButton(p, column, row) {
var b = buttonPosition(column, row);
return !(p.x < b.x ||
p.y < b.y ||
p.x > b.x + BUTTON_SIZE ||
p.y > b.y + BUTTON_SIZE);
};