Monday, May 18, 2015

Enabling Sound Effects on Web Pages

Got something to say?
I threw in a simple effect in today's comic where the computer is playing an acoustic guitar. Push the play, pause, and stop buttons above it, and you can control some real acoustic guitar music. What used to be a monumental task - putting sound effects in a web page - is now extremely simple.

It occurred to me that while I've mentioned using SoundManager2 several times in the blog, I haven't actually shown an example of its use since 2010! Things have changed since then, so it's worth bringing up again.

I like SoundManager2 because it's easy. I've been using it since before there was an HTML5 Audio specification, and have been very thankful for it. Abstraction solves all problems in computer science, and SoundManager2 provides a great abstraction on top of all the different ways to play sounds across different browsers on different types of devices. Sometimes it will use HTML5, sometimes it will revert to Flash (hopefully not often these days). But it just works.

When I made my game entry for the GitHub Game Off 2015, I used the Crafty JavaScript game framework. To play sounds across different platforms, I had to provide the sound effects in MP3, OGG, and WAV formats. It chose the correct one to play for whatever the client's capability happened to be. But back in 2013, my Game Off entry was coded from scratch and I used SoundManager2. I only had to provide sounds in MP3 format, which made my life easier.

Not that it's too difficult to convert sound formats with Audacity, but it was an extra step that I would have preferred to avoid. But I digress...

I've put sound effects in more than one of my comics recently and the pattern is always the same. First, simply include SoundManager's JavaScript file in your page. It comes with both debug and non-debug versions of the full-size and minimized versions. For production, I use soundmanager2-nodebug-jsmin.js.

<script src="soundmanager2-nodebug-jsmin.js"></script>

After that, playing sounds is extremely simple. Here is the complete guitar.js file from today's comic:

var guitarSound = null;

$(function() {

    soundManager.setup({
        url : '/swf/',
        onready : function() {
            guitarSound = soundManager.createSound({
                id : 'guitarSound',
                url : '/audio/guitar.mp3'
            });
        },
        ontimeout : function() {
            console.log("could not start soundmanager!");
        }
    });

    $('#play-button').click(function() {
        if (guitarSound) {
            if (guitarSound.playState === 0 || guitarSound.paused) {
                guitarSound.play();
            }
        }
    });

    $('#pause-button').click(function() {
        if (guitarSound) {
            guitarSound.togglePause();
        }
    });

    $('#stop-button').click(function() {
        if (guitarSound) {
            guitarSound.stop();
        }
    });

});

Since I also use jQuery, I do all the setup inside an anonymous function passed in to $(). This simply ensures that the code inside won't be called until the page is completely loaded and ready.

The call to soundmanager.setup on line 5 passes in an object describing the configuration of SoundManager. The url field is very important as it specifies where SoundManager can find the Flash files it needs if fallback to Flash becomes necessary. Those files are packaged in the SoundManager download file inside the /swf directory so I mimicked that path structure on my web site. The onready field is a function that will be called when SoundManager has completely initialized itself. This is where it is safe to create sounds which can be played later. In my case, I create one sound, guitarSound, from a file in my web application's /audio directlory named guitar.mp3. The ontimeout function, on line 13, is what gets called in the error condition that SoundManager could not start. It might happen if you try to use it on Netscape Navigator 4, or you just messed up your configuration.

The rest of the code just adds click listeners to the three types of buttons. I always check to see if my sound exists before doing anything with it, just in case SoundManager failed to start. The pause and stop functions are simple - just calling togglePause() and stop(). Toggle pause is different from the alternate pause() function in that it will also un-pause the sound if called when paused. The stop function should be self-explanatory. The handling of play is a little more complicated because SoundManager (on most modern platforms) allows you to play a sound multiple times and it will mix with itself. If you don't want that, check the playState before calling play() to ensure that it's not already playing. I also allow play() to be called if the sound is paused, which has the same effect as un-pausing.

It really is that easy. Sound can add an important dimension to human/computer interaction, and thanks to SoundManager2 it need not be excluded from the web-based user experience. Just don't make every button click play an annoying sound. Please.

Amphibian.com comic for 18 May 2015