Adding Sound to JS
colin-williams-dev
Posted on February 26, 2023
I am a lover of video games. I jump at every chance I get to incorporate video game compatibility to what I am learning in web development. Video games are complex and what separates the good from the bad is manifold. During my research I have encountered terms like "juice" which are "simple visual enhancements that make the game 'feel' good." (see my other blog here:). So, with that in mind, we need to consider the user experience while building our games. This experience stretches beyond the pillars of plot or gameplay mechanics and fractals out into minutia. Those minute details are often sensory, visually; like with "juice", or auditory; as we will explore below. For today's blog, I want to explore the latter and how the developer can intuitively create and add sounds to any JavaScript project using two libraries: JSFXR and Howler.
JSFXR
JSFXR is an online 8 bit sound maker and sfx generator. It features a super simple GUI for users to customize and create any lofi video game sound they could need:
With this free-to-use tool the developer can generate wave (.WAV) files that they can then host in their project environment or externally with a resource such as cloudinary. The sliders that JSFXR provides are very intuitive and robust, providing an interface for the user to create and manipulate detailed 8-bit sounds. JSFXR is maintained by a single developer named Chris McCormick. He is super informative and often posts youtube videos teaching developers how they can create games (often roguelikes...) with JavaScript or Python. I had the pleasure of speaking with him via Github Issues where he helped walk me through a struggle I was having. He responded within two minutes! of my late-night post! Incredible! Kudos to you Chris! The JSFXR team also thanks "Dr. Petter for inventing sfxr" and "Eric Fredricksen for creating [the sfxr port to JS]".
HOWLER
Once we have all of the sounds we want created we can then move back into our code editor. We will install Howler via npm: npm i howler
and obtain the super lightweight library: "7KB gzipped and is 100% JavaScript with no outside dependencies or plugins". The Howler team also boasts their library will work anywhere as it "...defaults to Web Audio and falls back to HTML5 Audio to provide full coverage across all browsers and platforms". This is great because between HTML5 and the Web Audio API most web-based audio options are covered. Using this library will allow the user a more intuitive process to curating where and how they want their sounds to be executed. Without this library, the developer will need to learn the syntax for Web Audio API and HTML5 with the DOM. It is wise to go down this path and learn the ropes, but once we are working with a front end library such as React that abstracts away the DOM for their own virtual DOM we want to avoid using the document
where we can to avoid availability and consistency problems. Furthermore, using a library like Howler that abstracts the process of connecting all the wires from our .wav files to the DOM makes working with sound files in JS a much more intuitive experience.
EXAMPLES
The following will be my most recent use of JSFXR and HOWLER in a project:
src/client/utility/sounds.ts
import { Howl, Howler } from 'howler';
We import Howl
which will be our sound constructor and Howler
as the library/namespace with methods for the sound execution.
const dodgeUrl = 'https://res.cloudinary.com/version/path/to/file'
We grab our hosted .wav file from cloudinary.
export const dodge = new Howl({
src: [dodgeUrl]
});
And we create our sound by linking the URL to cloudinary to the src of our new sound. We pass the URL inside an array because we can actually have multiple sound files inside the src. This allows for optimization depending on if a browser needs certain audio file types: (.mp3 vs .wav etc). There are even more ways to customize EACH sound we make with Howler like the following example:
var sound = new Howl({
src: ['sound.webm', 'sound.mp3', 'sound.wav'],
autoplay: true,
loop: true,
volume: 0.5,
onend: function() {
console.log('Finished!');
}
});
And finally, we can effect the master volume of all of our sounds directly on the Howler object like this:
Howler.volume(0.3);
Imagine how easily you can program a slider on the user side with this method!
Next is where and how we want our sounds to fire! For my project, I needed a sound to play when a user clicked a button. This button lived in my front end React component: GameView.tsx.
src/client/components/gameView/GameView.tsx
/* ... */
import { dodge } from '../../utility/sounds';
const GameView: React.FC = () => {
/* ... */
return (
/* ... */
<HudButton onClick={() => dodge.play()}>Continue</HudButton>
};
And that's it! We simply import the sound and call .play()
on it inside a react onClick attribute! There are other methods we can use as well such as .fade()
or .rate()
to further process or manipulate not only each sound but each instance a sound is fired!
Their docs are super user friendly and provide further insight into each use case: here.
// Play returns a unique Sound ID that can be passed
// into any method on Howl to control that specific sound.
let id1 = dodge.play();
let id2 = dodge.play();
// Fade out the first sound and speed up the second.
dodge.fade(1, 0, 1000, id1);
dodge.rate(1.5, id2);
Howler is so intuitive and flexible! From this point we can designate the execution of our sounds anywhere in our application. Anywhere we can invoke a method we can now execute a sound. Even as asynchronous code is being processed we could execute any number of sounds in an order we design.
In conjunction with JSFXR and Howler the developer can create an aural masterpiece with precision and ease.
Happy developing!
WORKS CITED
Posted on February 26, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.