DEV Community

Cover image for Quick and Easy 2D Spatial Audio with Howler.js
Jeff Puls
Jeff Puls

Posted on

Quick and Easy 2D Spatial Audio with Howler.js

Creating immersion within a web application is hard. Unless a lot of effort is put into the UI/UX design, these apps end up feeling very flat and lifeless. As such, even the smallest feature that adds an organic touch can vastly improve the "feel" of the app.

If your application is very audio-centric (a game for instance), one such feature you can easily add is spatial audio. Giving your sound effect an origin within the application can make the whole thing feel bigger. Let's take a quick look at how this can be achieved using a JavaScript audio library called Howler.

I won't go into details about how Howler itself works, but you can read up on the subject in their docs here. For now, all you need to know is that we use the Howl constructor to instantiate a simple sound effect object, and that this object takes an optional parameter called stereo.

The stereo parameter accepts a number ranging anywhere between -1 and 1, which corresponds to the left/right channel bias for the stereo sound (-1 being full left, 1 being full right). For this example, we simply want to play a sound effect when the mouse is clicked, and we want it to feel as though that sound originates from the cursor.

Below is the basic setup for use in a React component. This will play the specified sound effect normally whenever the mouse is clicked within the component.

import { useEffect } from "react";
import { Howl } from "howler";
import mySound from "./sounds/mySound.webm"; // our fictitious audio file, replace this with whatever sound you want to play

const MyComponent = () => {
  let component;

  useEffect(() => {
    const handleClick = (e) => {
      const sound = new Howl({ src: mySound }); // instantiate a new Howl here, passing it the path to our sound effect
      sound.play(); //  as soon as the object is created, we can play the sound effect
    };

    component && component.addEventListener("click", handleClick); //  once the component has been rendered and saved to a variable, add the EventListener

    return () => {
      component && component.removeEventListener("click", handleClick); //  if the component is removed, remove the EventListener
    };
  }, [component]);

  return (
    <div
      style={{ width: "100vw", height: "100vh" }} //  adding the styling ensures that our component will cover the entire viewport
      ref={(el) => (component = el)} // save the rendered element to a ref variable we can manipulate
    />
  );
};

export default MyComponent;

Enter fullscreen mode Exit fullscreen mode

Now, to figure out where the sound is coming from, we need to do some simple calculations based on coordinates of the cursor in relation to the width of the component. We will do so by adding the following function to the top of the useEffect callback.

const getStereoBias = (mouseX) => {
    const w = component.clientWidth; // grab the component's width
    const bias = -((Math.round(w / 2) - mouseX) / w) * 2; // calculate a value of -1 to 1 based on the cursor position within the component
    return bias;
  };
Enter fullscreen mode Exit fullscreen mode

And finally, we will use this function whenever a sound effect is generated to tell Howler where the sound is coming from by modifying the handleClick function as follows.

 const handleClick = (e) => {
    const stereoBias = getStereoBias(e.clientX); //  calculate the "position" of the sound's origin

    const sound = new Howl({ src: mySound, stereo: stereoBias }); // instantiate a new Howl here, passing it the path to our sound effect and stereo bias "position"
    sound.play(); //  as soon as the object is created, we can play the sound effect
  };
Enter fullscreen mode Exit fullscreen mode

And just like that, whenever our sound effect is played, it will follow the cursor around the screen (handy for things like particle effects in games)!

mind blown

To view a fleshed-out example of this concept in action, check out my Domain Destroyer Demo project.

If you make something cool like this, drop it in the comments, I'd love to see what you come up with!

Latest comments (2)

Collapse
 
mactunechy profile image
Dellan Muchengapadare

So I'm trying to create a virtual town and I want to use spatial audio with streams such people close to each othe in the online town hear each other's streams and if they're far apart the dont, is this achievable with howler?

Collapse
 
jpuls profile image
Jeff Puls

It depends how exactly your project is set up, but I'm sure you could make something work. Howler actually includes a full 3D spacial audio plugin for games and such.

You can check that out here