tutorial // Aug 27, 2021
How to Build a Soundboard with JavaScript
How to build a soundboard in JavaScript by creating a SoundPlayer class that dynamically injects players and makes it easy to map their playback to a DOM event.

Getting Started
For this tutorial, we're going to use the CheatCode Next.js Boilerplate as a starting point for our work. To start, let's clone a copy:
Terminal
git clone https://github.com/cheatcode/nextjs-boilerplate
Next, cd
into the project and install its dependencies:
Terminal
cd nextjs-boilerplate && npm install
Finally, start up the development server:
Terminal
npm run dev
With all of that, we're ready to get started.
Building a sound player
In order to actually play the sounds in our soundboard, we'll want an easy way to create audio players on-the-fly. To do that, we're going to begin by wiring up a JavaScript class that will handle the creation of the <audio></audio>
elements that will play our sounds and automate the injection of those elements into the DOM.
/lib/soundPlayer.js
class SoundPlayer {
constructor() {
this.sounds = [];
}
// We'll implement the API for our class here...
}
export default SoundPlayer;
To start, here, we're creating a skeleton for our SoundPlayer
class which will help us to load sounds into the DOM as well as play those sounds. Here, we set up a basic JavaScript class
and export it as the default from /lib/soundPlayer.js
.
Inside the class
, we add the constructor
function (this is what gets called right as our class is loaded into memory by JavaScript) and initialize the sounds
property on the class, setting it to an empty []
array. Here, this
is referring to the current class instance of SoundPlayer
. We're creating an array here as we'll want a way to keep track of all the sounds we've loaded into the DOM.
Terminal
class SoundPlayer {
constructor() {
this.sounds = [];
}
load(name, path) {
this.sounds = [...this.sounds, { name, path }];
this.injectPlayerIntoPage(name, path);
}
injectPlayerIntoPage(name, path) {
const player = document.createElement("audio");
player.id = name;
player.src = path;
player.volume = 0.5;
player.type = "audio/mpeg";
document.body.appendChild(player);
}
}
export default SoundPlayer;
Next, we need a simple API (application programming interface, here used colloquially to mean "the implementation of the player") for loading sounds into the DOM. To do it, above, we're adding two methods to our class: load()
and injectPlayerIntoPage()
. The first will be a publically-exposed function that we'll call from our UI to say "load this sound into the DOM."
Inside of that function, we can see two things happening. First, like we hinted at above, we want to keep track of the sounds we're loading in. Taking in a name
argument (an easy-to-remember name to "label" our sound by) and a path
(the literal path to the sound file in our app), we overwrite the this.sounds
property on our class to be equal to the current value of this.sounds
, concatenated with a new object containing the name
and path
passed into load()
.
Here, ...this.sounds
is "unpacking" the entirety of the existing this.sounds
array (whether or not it contains anything). The ...
part is known as the spread operator in JavaScript (it "spreads out" the contents of the value immediatelly following the ...
).
Next, with our this.sounds
array updated, we need to dynamically create the <audio></audio>
element we talked about above. To do it, we're adding a separate method injectPlayerIntoPage()
which takes in the same two arguments from load()
, name
and path
.
Inside of that function, the first thing we need to do is create the <audio></audio>
element in memory. To do it, we run document.createElement('audio')
to instruct JavaScript to create an in-memory (meaning not added to the screen/DOM yet) copy of our <audio></audio>
element. We store the result of that (the in-memory DOM node for our <audio></audio>
element) in the variable const player
.
We do this to more easily modify the attributes of the player and then append it to the DOM. Specifically, we set four properties to our player
before we append it to the DOM:
id
which is set to thename
we passed in for our sound.src
which is set to thepath
to the file on the computer for the sound.volume
which is set to0.5
or 50% to ensure we don't shatter our user's ear drums.type
which is set to the file type we expect for our files (for our example, we're using.mp3
files so we used theaudio/mpeg
MIME-type-find others here).
Once we've set all of these properties, finally, we use appendChild
on document.body
to append our audio player to the DOM (the physical location of this in the DOM is irrelevant as we'll learn next).
/lib/soundPlayer.js
class SoundPlayer {
constructor() {
this.sounds = [];
}
load(name, path) {
this.sounds = [...this.sounds, { name, path }];
this.injectPlayerIntoPage(name, path);
}
injectPlayerIntoPage(name, path) {
const player = document.createElement("audio");
player.id = name;
player.src = path;
player.volume = 0.5;
player.type = "audio/mpeg";
document.body.appendChild(player);
}
play(name) {
const player = document.getElementById(name);
if (player) {
player.pause();
player.currentTime = 0;
player.play();
}
}
}
export default SoundPlayer;
To wrap up our SoundPlayer
class, we need to add one more method: play()
. Like the name suggests, this will play a sound for us. To do it, first, we take in a name
argument (one that we would have passed into load()
earlier) and try to find an element on the page with an id
attribute matching that name.
Recall that above we set the .id
on our <audio></audio>
tag to the name
we passed in. This should find a match in the DOM. If it does, we first .pause()
the player (in case we're mid-playback already), force the .currentTime
attribute on the player to 0
(i.e., the start of our sound), and then .play()
it.
That does it for our SoundPlayer
class. Next, let's wire it up and start to play some sounds!
Adding a React page component to test our player
Because our boilerplate is based on Next.js, now, we're going to create a new page in our app using a React.js component where we can test out our SoundPlayer
.
/pages/soundboard/index.js
import React from "react";
import SoundPlayer from "../../lib/soundPlayer";
class Soundboard extends React.Component {
state = {
sounds: [
{ name: "Kick", file: "/sounds/kick.mp3" },
{ name: "Snare", file: "/sounds/snare.mp3" },
{ name: "Hi-Hat", file: "/sounds/hihat.mp3" },
{ name: "Tom", file: "/sounds/tom.mp3" },
{ name: "Crash", file: "/sounds/crash.mp3" },
],
};
componentDidMount() {
const { sounds } = this.state;
this.player = new SoundPlayer();
sounds.forEach(({ name, file }) => {
this.player.load(name, file);
});
}
render() {
const { sounds } = this.state;
return (
<div>
{sounds.map(({ name, file }) => {
return (
<button
className="btn btn-primary btn-lg"
style={{ marginRight: "15px" }}
onClick={() => this.player.play(name)}
>
{name}
</button>
);
})}
</div>
);
}
}
Soundboard.propTypes = {};
export default Soundboard;
In Next.js, routes or URLs in our app are automatically created by the the framework based on the contents of the /pages
folder at the root of our app. Here, to create the route /soundboard
(this will ultimately be accessible via http://localhost:5000/soundboard
in the browser), we create the folder /pages/soundboard
and put an index.js
file in that folder where the React component representing our page will live.
Because our test component is so simple, above, we've output the entire contents. Let's step through it to understand how all of this is fitting together.
First, up top we import our SoundPlayer
class from our /lib/soundPlayer.js
file.
Next, we define a React component using the class-based method (this makes it easier to work with our player and avoid performance issues). The first part we want to call attention to is the state
property we're adding to the class and the sounds
property we've set to an array of objects there.
This should be starting to make some sense. Here, we're creating all of the sounds that we want to load into the DOM using the load()
method we wrote earlier on our SoundPlayer
class. Remember, that function takes a name
and a file
argument which we're defining here.
We do this as an array of objects to make it easier to loop over and load all of our sounds at once, which we do in the componentDidMount()
function on our React component. In there, we use JavaScript object destructuring to "pluck off" the sounds
property we just defined on state
(accessible in our component's methods as this.state
) and then create an instance of our SoundPlayer
class with new SoundPlayer()
and then assign that instance back to this.player
on our Soundboard
component class (this will come in handy soon).
Next, using that sounds
array we defined on state, we loop over it with a .forEach()
, again using JavaScript destructuring to "pluck off" the name
and file
properties of each object in the array as we loop over them. With these values, we call to this.player.load()
, passing them into the function. Like we learned earlier, we expect this to add each of the sounds
in our array to the this.sounds
array on our SoundPlayer
class' instance and then append a DOM element for that sound's <audio></audio>
player.
Where this all comes together is down in the render()
method on our component class. Here, we again "pluck off" the sounds
array from this.state
, this time using a JavaScript .map()
to loop over the array, allowing us to return some markup that we want React to render for each iteration (each sound) of our array.
Because we're building a soundboard, we add a <button></button>
for each sound with an onClick
attribute set to a function which calls this.player.play()
passing in the name
attribute from the sound's object in the this.state.sounds
array. With this, we have a soundboard!
Now when we click on a button, we should hear the associated sound in the file play back.
Download a .zip containing all of the sounds referenced in the code above and unzip its contents into your app's
/public/sounds
folder from CheatCode's Amazon S3 Bucket.
That's it! If you'd like to add your own custom sounds, just make sure to add them to the /public/sounds
folder in your app and then update the sounds
array on state.
Wrapping up
In this tutorial, we learned how to create a soundboard using JavaScript. To do it, we began by creating a JavaScript class that helped us to dynamically create audio players that we could reference by a unique name. On that class, we also added a .play()
method to streamline the playback of our sounds.
To build the UI for our soundboard, we defined a React component that created an instance of our soundboard class, loaded in our preferred list of sounds, and then rendered a list of buttons, each with a call to the .play()
method for the sound represented by that button.