Howler Soundboard

Anti Ambient Soundboard

About

Thanks to Howler.js, audio on the web is a relative breeze. In this post, I'll show you how to setup howler.js, create a simple soundboard and add some basic howler functionality to play, stop and adjust volume for a group of audio files.

Howler.js has tons of great features and capabilities. I'm only scratching the surface. The code below is based on a soundboard project I made — Anti-Ambient. Unfortunately, very fitting for the pandemic times we live in.

As for the audio files, I used Adobe Audition to create my loops. Exported them as wav files, then converted them to mp3, then used ffmpeg to generate webm files.

One Sound

The docs recommend using webm for covering most browsers and mp3 for Internet Explorer fallback suport. Following the docs, we can create a new Howl object, provide our sources and set html5 to true for large audio files to play before being completely downloaded. This helps if the user has clicked on the play button before the file has finished, it will still play! Since I'll be looping these files, I've set that to true. The volume is set to 0, which we will fade up later in the code.

      
          new Howl({
             src: ['./sounds/gentrification-construction.webm','./sounds/gentrification-construction.mp3'],
             volume: 0,
             html5: true,
             loop: true
          })
      

Multiple Sounds

Howler has a global object Howler that controls all sounds. A Howl as defined above falls under this parent object. Each Howl has a whole list of methods that can be called for just that sound instance. To see how we can get an array of sounds for our soundboard, let's use an object to store them. The paths to the audio files are relative to the javascript file we are calling them from. Each sound is stored in an object with a descriptive name which we will connect to our markup.

      
        const soundboardAudio = {
          construction: {
           sound: new Howl({
             src: ['./sounds/gentrification-construction.webm','./sounds/gentrification-construction.mp3'],
             volume: 0,
             html5: true,
             loop: true
           }),
           details: "gentrification construction"
         },
         protest: {
           sound: new Howl({
            src: ['./sounds/standing-rock-protest.webm','./sounds/standing-rock-protest.mp3'],
            volume: 0,
            html5: true,
            loop: true
          }),
          details: "standing rock protest"
          },
          sweatshop: {
           sound: new Howl({
             src: ['./sounds/sweat-shop.webm','./sounds/sweat-shop.mp3'],
             volume: 0,
             html5: true,
             loop: true
           }),
           details: "sewing sweat shop"
         },
       };
      

The Markup

Each button and the volume range slider will contain a data-sound="name-here" attribute that maps to the soundboardAudio object above. With some javascript, we'll get the attribute, find the matching sound in our javascript object and play, stop and adjust the volume. Here is the markup and styles for our 3 icons.

See the Pen howler-soundboard by Nick (@telagraphic) on CodePen.

Playing and Stopping

Since this is a simple soundboard, we just need to play and stop a specific sound when the user clicks the button. First, let's select all the .sound-box elements that will act as a play/stop button and add an event listener that calls the toggleButtonSounds.

      
        function setupSoundButtons() {
          const allButtons = selectAll('.sound-box');

          allButtons.forEach(button => {
            button.addEventListener('click', toggleButtonSounds);
          });
        }
      

That selectAll function is a cool convenient way to wrap document.querySelectorAll. I learned this from Carl Schoof, the GSAP maestro.

      
        let select = e => document.querySelector(e);
        let selectAll = e => document.querySelectorAll(e);
      

The toggleButtonSounds has got several things going on, let's take a look at each part. First, it is important to understand the difference between event.target and event.currentTarget. The element that 'fires' the click event is the target, it is the element that was clicked and bubbles up thru the DOM to where we are listening on the currentTarget, the element that we attached the event listener to.

      
        function toggleButtonSounds(event) {
          let buttonSound = event.currentTarget.dataset.sound;
          let currentVolume = event.currentTarget.querySelector('.sound-box__volume-slider').dataset.volume;

          if (event.target.matches('.sound-box__volume') || event.target.matches('.sound-box__volume-slider')) {
            return;
          }

          if (soundboardAudio[buttonSound].sound.playing()) {
            soundboardAudio[buttonSound].sound.fade(currentVolume, 0, fadeTimeout);

            setTimeout(function() {
              soundboardAudio[buttonSound].sound.stop();
            }, fadeTimeout);

          } else {
            soundboardAudio[buttonSound].sound.play();
            soundboardAudio[buttonSound].sound.fade(0, currentVolume, 1000);
          }
        }
      

The first two lines in the function use event.currentTarget to select our .sound-box component. buttonSounds will be used to find the matching audio sound in the soundboardAudio object. We grab a data attribute for the sound name and range slider volume attribute. The currentTarget is the element we added the event listener on and if I were to make changes to the DOM layout within .sound-box it won't break my code. Not the best example but useful.

The first if statement checks to see if the target is a a volume element, if so, we return and do not proceed. If the user is clicking the volume slider, I don't want the rest of the code to run since it controls the play/stop functionality. We'll see the volume function in the next part.

The second if is where we play/stop the actual sound. The first line in the function grabs the data attribute from the markup and we use that to access the howl sound object in the soundboardAudio obect. The playing() method returns a boolean of true if the sound is playing or false if it is not. If the sound is playing, the fade() function will grab our current volume and fade to 0. I then call stop() within a setTimeout to make sure the fade actually audibly fades before calling the stop() method. If I didn't use a setTimeout and called stop() right after the fade(), the fade would be cut short.

The else statement will run if the sound is not playing. We simply play() the sound and fade() it up using the currentVolume attribute in our HTML. This attribute kinda acts like a simple state variable.

Volume Control

Each sound has it's own volume slider, the function below grabs another data attribute on the range input to change the volume for that specific sound by mapping it to the correct howl sound object in the soundboardAudio object. The last line then sets a volume data attribute so that when the button is stopped and then played again, it will use the previous volume.

      
        function setupVolumeSliders() {
          const volumeSliders = selectAll('.sound-box__volume-slider');

          volumeSliders.forEach(slider => {
            slider.addEventListener('click', event => {
              let sliderSound = event.target.dataset.sound;
              let currentVolume = event.target.value;
              soundboardAudio[sliderSound].sound.fade(event.target.dataset.volume, currentVolume, 100);
              event.target.dataset.volume = event.target.value;
            })
          })
        }
      

Stop It All

Let's say we are playing all the sounds and it's getting to be too much, let's make them all stop. This is easy. We simply call Howler.stop() to stop any sounds being actively played. The rest of the function is just re-setting UI styles on elements back to their normal condition.

      
        function setupStopButton() {
          const stopButton = select('.header__stop-button');
          stopButton.addEventListener('click', function(event) {
            Howler.stop();

            let soundButtons = selectAll('.sound-box');
            soundButtons.forEach(button => {
              button.querySelector('.sound-box__header').style.opacity = 0;
              button.querySelector('.sound-box__icon').style.opacity = 1;
              button.querySelector('.sound-box__volume').style.opacity = 0;
              button.querySelector('.sound-box__volume-slider').dataset.volume = ".5";

              setTimeout(function() {
                button.querySelector('.sound-box__volume-slider').value = .5;
              }, 1500);

            });
          });
        };
      

There you have it, now you can make your own soundboard. To see the full demo of the code above, check out Anti-Ambient.