Sunday, February 24, 2013

Playing, Pausing and Restarting Sounds in Three.js

‹prev | My Chain | next›

I had a good time with HTML5 sounds last night. I really had no idea that it was so easy. I am still unsure if I will end up including sound in 3D Game Programming for Kids, but it seems worth building up a simple Sounds.js library.

I spend a good amount of time trolling through the freesound.org for a selection of sounds. I end up with the following list (sound name, attribution, freesound filename):
bubble.mp3: CGEffex // 89534__cgeffex__very-fast-bubble-pop1.mp3
buzz.mp3: Sikoses // 82756__sikoses__stm1-upbass-5.mp3
click.mp3: orginaljun.deviantart.com // 157871__orginaljun__arcade-button-1-click-sound.mp3
donk.mp3: orginaljun.deviantart.com // 153063__orginaljun__1-bell-sound.mp3
drip.mp3: orginaljun.deviantart.com // 151840__orginaljun__pie-poi-water-dripping-sound.mp3
guitar.mp3: johnnypanic // 158650__johnnypanic__quickpowerchord2.mp3
knock.mp3: blimp66 // 88658__blimp66__knock.mp3
scratch.mp3: orginaljun.deviantart.com // 152844__orginaljun__1-rub-scratch-sound.mp3
snick.mp3: orginaljun.deviantart.com // 151482__orginaljun__weechuuu-high-pitch-sound-version-02.mp3
spring.mp3: orginaljun.deviantart.com // 153490__orginaljun__rusty-spring.mp3
swish.mp3: orginaljun.deviantart.com // 153489__orginaljun__friction-moving-object.mp3
It is not an exhaustive list, but hopefully a good start for kids / beginners building their first games.

I push each of those sounds under the http://gamingjs.com/sounds urlspace. With that, I need a Sound class that can play any of them:
  function Sound(name) {
    this.name = name;
    this.audio = document.createElement('audio');
    var source = document.createElement('source');
    source.src = '/sounds/' + name + '.mp3';
    this.audio.appendChild(source);
  }
  
  Sound.prototype.play = function() {
    this.audio.play();
  };
This is just an object representation of last night's work. Also in the Sound prototype, I add the ability to repeat the sound, as well as stopping it:
  Sound.prototype.repeat = function() {
    this.audio.loop = true;
    this.audio.play();
  };

  Sound.prototype.stop = function() {
    this.audio.repeat = false;
    this.audio.pause();
  };
Since the constructor takes the sound name, which is cleverly the basename of the MP3 file containing the sound, I can define the list of all sounds as:
  var Sounds = {
    all: [
      'bubble','buzz','click','donk',
      'drip','guitar','knock','scratch',
      'snick','spring','swish'
    ]
  };
And then add each of these sounds to the Sounds list:
  Sounds.all.forEach(function(sound) {
    Sounds[sound] = new Sound(sound);
  });
With that, I can redefine my Three.js / Physijs collision event handler to play a spring noise as:
  player.addEventListener('collision', function(object) {
    Sounds.spring.play();
  });
I rather like that. Except that it does not always play on collision. In particular, when a collision occurs immediately after another collision, the previous sound is still playing, blocking the new collision sound. To get around this, I stop the sound whenever I start it:
  Sound.prototype.play = function() {
    this.stop();
    this.audio.play();
  };
Furtermore, in the stop() method, I need to reset the current playback time to zero:
  Sound.prototype.stop = function() {
    this.audio.repeat = false;
    this.audio.currentTime = 0;
    this.audio.pause();
  };
And that seems to do the trick.

I close by verifying that repeat mode works:
  Sounds.swish.repeat();
  setTimeout(function(){Sounds.swish.stop();}, 8000);
And work it does.

If nothing else, this seems worth including in an appendix. I have to think that kids are going to want to include sounds in their games. I must say that I really am surprised at how easy this sound stuff is—I had avoided it so far assuming that it would be much tougher.

(a simple live code sound demo)

Day #672

1 comment:

  1. The 'loop' attribute leaves a gap, so you can't hear it seamlessly.

    ReplyDelete