HTML5 audio for games made easy

Published October 20, 2014
Advertisement
Audio in the browser is deceptively tetchy. It's easy to get a basic sound to play with the tag.[code=html:1] If you can read this, your browser is not fully HTML5 compatible.
But there are several problems with this:

  • First of all, good luck navigating this compatibility chart. Until Mozilla finally caved and decided to support MP3, there was no single file format supporting Chrome, Firefox, Safari, and IE. Opera is still a painful holdout on file formats, and the situation on mobile is disgusting.
  • You can't programmatically trigger the audio playing on mobile devices without direct user action in your game loop. You basically have to put up a "start game" button that tricks the user into playing a silent audio file, then you have free reign to trigger that particular audio element.
  • You get almost no control over how the file plays. There is a volume setting, but it hasn't always been reliable on all platforms. There's no mixing. There are no effects.
  • It's super difficult to rewind and replay an audio file. Honestly, I still don't really know how to do it correctly, and I'm a goddamn salty pirate.

In short, the tag is for one thing and one thing only: for NPR to post their podcasts directly on their site.

Okay, let's get out of this malarky. What else do we have? Well, there's the Web Audio API:

  • Granted, formats still aren't great. Strangely, the compatibilities don't exactly match the tag. But MP3 is universally there, as is AAC. And technically, you could write a decoder if you wanted. I wouldn't suggest it, but it is possible.
  • You can play audio whenever you want, as many times as you want, on desktop and mobile, without buggering around with stupid hacks.
  • It's a fairly-well featured signal processing system. That's great if you know what you're doing, murder if you don't.

It's a little difficult to program. And the MDN tutorial gets far too into crazy effects for me to bother if all I want to do is make a few blips, bloops, and gunshot sounds.

That's why I wrote this: Audio3DOutput.js. Here's what you do:[code=js:1]// to start, create the audio contextvar audio = new Audio3DOutput();// then, check if your system supports itif(audio.isAvailable){ // if you want to play a specific sound file every time a user clicks a mouse button: audio.loadBuffer("click.mp3", null, function(buffer){ window.addEventListener("mousedown", function(evt){ audio.playBufferImmediate(buffer, 0.25); // 25% volume gain }); }); // if you want progress notifications while the audio is loading and processing: audio.loadFixedSound("song.mp3", /* looping */ true, function(op, file, numBytes){ console.log(op, file, numBytes); }, function(snd){ snd.source.start(); }); // if you want to position the sound in 3D space: var sourceX = 10, sourceY = -4, sourceZ = 3; audio.load3DSound("ambient-sound.mp3", true, sourceX, sourceY, sourceZ, null, function(snd){ snd.source.start(); setTimeout(moveListener, 5000); // 5 seconds }); function moveListener(){ audio.setPosition(x, y, z); audio.setVelocity(vx, vy, vz); audio.setOrientation( ox, oy, oz, upz, upy, upz); } // if you want to take the first file of a list that successfully loads: audio.loadFixedSoundCascadeSrcList(["song.aac", "song.ogg", "song.mp3"], null, function(snd){ snd.source.start(); }); // or if you want to synthesize the raw PCM data yourself: // in monaural var data = [], seconds = 0.25; for(var i = 0; i <= audio.sampleRate * seconds; ++i){ data.push((Math.sin(i / 10) + 1) * 0.5); } audio.createRawSound([data], function(buffer){ window.addEventListener("keydown", function(evt){ if(evt.keyCode === 81){ audio.playBufferImmediate(buffer, 0.05); } }); }); // in stereo var left = [], right = []; for(var i = 0; i <= audio.sampleRate * seconds; ++i){ left.push((Math.sin(i / 10) + 1) * 0.5); right.push((Math.sin(i / 20) + 1) * 0.5); } audio.createRawSound([left, right], function(buffer){ window.addEventListener("keydown", function(evt){ if(evt.keyCode === 81){ audio.playBufferImmediate(buffer, 0.05); } }); });}
There you go. If you find a need for more than these basic functions, please drop me a line and let's discuss adding it!
4 likes 6 comments

Comments

EDI

Good post; I've been doing html5 game stuff since early 2012 and its come a long way since then.

While a bit out of scope it might be worth noting that doing singular file requests for sound effects; is likely to result in poor performance in a production environment.

I've found instead using an 'audio atlas' which is a pre-made 1D array of sound effect files; with an associated mapping of file id to start position and duration.

This way, to play a sound you simply instruct webaudio to seek to a position and play to a duration.

-Raymond

October 21, 2014 01:41 PM
capn_midnight

That's a good idea. And yes, with Web Audio, it shouldn't be difficult.

My goal with this was to just get a bare minimum of useful audio together. The Web Audio API is extensive, but for throwing together quick demos, 80% of it is unnecessary. This JS file is for that 20% use case, wherein the <audio> tag is completely useless.

October 21, 2014 09:09 PM
tnovelli

For a completely different approach, you can generate retro sound effects (like BFXR) with WebAudio... I made a decent start: http://tnovelli.net/junk/synth2.html

October 22, 2014 01:43 AM
capn_midnight

Very nice, but you shouldn't use <progress> elements like that. You should be using <meter>.

October 22, 2014 01:52 AM
Eck

Very cool post sir. Thanks for sharing.

October 23, 2014 02:37 AM
capn_midnight

You're welcome!

October 24, 2014 01:32 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement