How can I get a (very) short .mp3 audio file to play on every keystroke?

Viewed 673

I am building a web-app which includes a form and (as a progressive enhancement / UX effect) I would like a keypress to fire a keystroke sound effect.

I have cut the .mp3 sound effect quite short (0.18s).

Nevertheless, there appears to be an audible delay when I test my setup on Firefox on my laptop (and not every keypress fires the sound effect).

On Firefox Mobile on my Android, very few keypresses fire the sound effect - maybe one keypress in every eight.

Am I trying to achieve something which can't be done using present web technology, or am I simply approaching this the wrong way?

Here is my setup:

var myInput = document.getElementsByTagName('input')[0];
var keypress = document.getElementsByClassName('keypress')[0];

function playSoundEffect() {
    keypress.play();
}

myInput.addEventListener('keydown', playSoundEffect, false);

/* I've also tried...
myInput.addEventListener('keyup', playSoundEffect, false);
myInput.addEventListener('keypress', playSoundEffect, false);
*/
input {
width: 90vw;
}
<input type="text" placeholder="Type to hear the sound effect..." />

<audio class="keypress">
<source src="http://handsoffhri.org/.assets/theme/elements/mp3/keypress.mp3" type="audio/mpeg">
</audio>


Second Attempt:

Thanks to the helpful comments below from @StackingForHeap and @zero298, I have refactored my setup:

var myInput = document.getElementsByTagName('input')[0];

function playSoundEffect() {
    var jsKeypress = new Audio('http://handsoffhri.org/.assets/theme/elements/mp3/keypress.mp3');
    jsKeypress.play();
}

myInput.addEventListener('input', playSoundEffect, false);
input {
width: 90vw;
}
<input type="text" placeholder="Type to hear the sound effect..." />

This is definitely an improvement - creating a new Audio object each time allows for each one to start playing independently of any previously created Audio objects.

2 Answers

I try doing the same process with the howler.js library, and it seems to work very well. this is the example

 var myInput = document.getElementsByTagName('input')[0];
  var sound = new Howl({
    src:['http://handsoffhri.org/.assets/theme/elements/mp3/keypress.mp3']
  });

  sound.play();


  function playSoundEffect() {
    sound.play();
   }

  myInput.addEventListener('keydown', playSoundEffect, false);

I have taken a look at your second attempt and it seems sound (excuse the pun). However, I just took a crack at it and I am able to get pretty good results with this:

This uses an audio file that I found on Freesound.org: UI Confirmation Alert, C4.wav.

This creates an AudioContext instead of just an Audio and uses the same, already loaded, buffer over and over again. I'm still trying to see if there is a performance gain there.

Looks like you have to make source nodes over and over again:

An AudioBufferSourceNode can only be played once; after each call to start(), you have to create a new node if you want to play the same sound again. Fortunately, these nodes are very inexpensive to create, and the actual AudioBuffers can be reused for multiple plays of the sound. Indeed, you can use these nodes in a "fire and forget" manner: create the node, call start() to begin playing the sound, and don't even bother to hold a reference to it. It will automatically be garbage-collected at an appropriate time, which won't be until sometime after the sound has finished playing.

/*jslint browser:true, esversion:6, devel:true*/
/*global AudioContext*/

(function() {
  "use strict";

  function main() {
    let ctx = new AudioContext();

    fetch("/res/audio/twinkle.wav")
      .then(res => res.arrayBuffer())
      .then((buffer) => {
        return new Promise((resolve, reject) => {
          ctx.decodeAudioData(buffer, (audioBuffer) => {
            resolve(audioBuffer);
          });
        });
      })
      .then((audioBuffer) => {
        document.addEventListener("keydown", (e) => {
          console.log(e.keyCode);
          if (e.keyCode === 37) { // The "left arrow" key
            let source = ctx.createBufferSource();
            source.buffer = audioBuffer;
            source.connect(ctx.destination);
            source.start(0);
          }
        });
      });
  }
  document.addEventListener("DOMContentLoaded", main);
}());

Related