4

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.

Rounin
  • 27,134
  • 9
  • 83
  • 108
  • 2
    Maybe check https://stackoverflow.com/questions/1933969/sound-effects-in-javascript-html5 for ideas. Dedicated API is the best way, but you can manage without too if you have to. – Stacking For Heap Oct 04 '17 at 16:41
  • 4
    I would suggest not trying to get an actual audio element playing, and instead just route directly through the [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API) since you're already using JavaScript. You might get less latency with it. – zero298 Oct 04 '17 at 16:47
  • Thanks both of you - I have refactored (see **Second Attempt**, above). The effect is certainly much better. I'm wondering if there is any way I can decrease the delay in fetching the sound file. (I accept that there may not be). – Rounin Oct 04 '17 at 17:06
  • 1
    Can you look at the actual waveform of the audio file that you have? You probably already have, but double check that there is absolutely no leading or trailing silence in your file. Also, maybe convert it to wav which might load more quickly. Also look for ways to create the Audio objects ahead of time and keep a buffer of them so that you don't have to make new ones for every key. – zero298 Oct 04 '17 at 17:07
  • 1
    @Rounin The audio file itself seems to be about 0.7 seconds long. Perhaps you want to try a shorter clip. – Asad Saeeduddin Oct 04 '17 at 17:07
  • It's 0.18s long, but I'll try to make it even shorter. – Rounin Oct 04 '17 at 17:12
  • Done it! Thanks @zero298 and @AsadSaeeduddin - the combination of reducing the length of the sound file even further combined with using an `input` event, combined with creating new `Audio` objects directly in javascript means that it now works perfectly. Whoever wants to write the answer below (you can copy-paste the **Second Attempt** above) can have the points. Thank you, all. – Rounin Oct 04 '17 at 17:29

2 Answers2

1

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);
Cristyan
  • 650
  • 4
  • 15
1

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);
}());
zero298
  • 25,467
  • 10
  • 75
  • 100