Playing Music and Sound Effects in a Windows 8 Metro Style App using HTML and JavaScript
Overview
I have recently been coding a Windows 8 Metro Style App using the new Windows 8 Release Preview bits and Visual Studio Express 2012 RC. The app is going to be a retro shooter that takes advantage of HTML5 Canvas for the main game engine and then several Windows 8 Metro Style App Features.
What game would rock without Music and Sound Effects! So how do we add that functionality to a Metro Style App? For my game I decided to take two routes.
This is how I broke everything down:
- HTML5 Audio is being used for the background music and is in a wav file format. I have two royalty free music tracks that play one during my game’s menu screen and one during actual gameplay.
- All Sound Effects are played using the free SoundJS Library. This JavaScript Library works straight off gitHub and no additional work was needed to use it inside our Windows Metro Style App!
- The Lasers are three separate sound effects and each time a ship is destroyed a random one will play. They are royalty free sounds I pulled from some of the XNA samples via AppHub’s Game Development Library.
- The announcer voices were custom recorded by me and saved as mp3 files via the free Audacity Sound Editing Tool. I got the retro voice sound (anyone remember AdLib and SoundBlaster back in the day?) by tweaking the stream.
Declaration and Initialization
I store all of my sound and music files in a /sounds folder off the root of the project and include the sound.js file in my normal /js folder.
Inside of my default.html file I then make a reference to the SoundJS library.
<link href="/css/default.css" rel="stylesheet">
<link href="/css/space.css" rel="stylesheet"/>
<script src="/js/default.js"></script>
<script src="/js/sound.js"></script>
Our App is now ready to play music and sound streams.
Music
Inside of my default.js file I set up a couple of local variables to handle the streams and the sound effects we will pass to SoundJS. I also set up a variable to detect if I have any music already playing.
//Menu Music
var musicPlaying = false;
var musicMenu = new Audio("/sounds/hydrogen.mp3");
var musicGame = new Audio("/sounds/crazy_comets.wav");
musicMenu.loop = true;
musicGame.loop = true;
//soundeffects
var lasers = new Array();
lasers[0] = "laser1";
lasers[1] = "laser2";
lasers[2] = "laser3";
During my initialize function I set up all of my game parameters and load the sound effects I will use into SoundJS. SoundJS lets you optionally load multiple instances of the same stream but I’m choosing just to use the one right now.
//Init Sounds
SoundJS.addBatch([
{ name: "redalert", src: "../sounds/redalert.mp3", instances: 1 },
{ name: "newlevel", src: "../sounds/newlevel.mp3", instances: 1 },
{ name: "pulse", src: "../sounds/pulse.mp3", instances: 1 },
{ name: "laser1", src: "../sounds/laser1.mp3", instances: 1 },
{ name: "laser2", src: "../sounds/laser2.mp3", instances: 1 },
{ name: "laser3", src: "../sounds/laser3.mp3", instances: 1 }]);
I then load up the UI elements for my Game Menu and begin playing my Menu music. This is done by simply calling the pause() and play() methods of both the musicGame and musicMenu HTML5 Audio tag variables we declared previously.
Here is the code that initializes the Menu UI and begins playing the Menu Music:
//Set Up Menu Screen UI Elements
function showMenu(event) {
menuEnabled = true;
txtPlayerName.style.visibility = "hidden";
txtScore.style.visibility = "hidden";
imgPlayer.style.visibility = "hidden";
imgMenu.style.visibility = "visible";
btnStart.style.visibility = "visible";
txtVersion.innerHTML = GAME_VERSION;
txtVersion.style.visibility = "visible";
txtLevel.style.visibility = "hidden";
var menuX, btnX, btnY;
menuX = (SCREEN_WIDTH - imgMenu.width) / 2;
btnX = (SCREEN_WIDTH - btnStart.clientWidth) / 2;
btnY = (SCREEN_HEIGHT - btnStart.clientHeight) / 2;
imgMenu.style.posLeft = menuX;
btnStart.style.posLeft = btnX;
btnStart.style.posTop = btnY;
musicGame.pause();
musicMenu.play();
}
Here is the code that initializes the Game UI and begins playing the Game Music:
/Set up Game Screen UI Elements
function startGame(event) {
txtPlayerName.style.visibility="visible";
txtScore.style.visibility="visible";
imgPlayer.style.visibility = "visible";
imgMenu.style.visibility = "hidden";
btnStart.style.visibility = "hidden";
txtVersion.style.visibility = "hidden";
txtLevel.style.visibility = "visible";
var lvlX = (SCREEN_WIDTH - txtLevel.clientWidth) / 2;
txtLevel.style.posLeft = lvlX;
musicMenu.pause();
musicGame.play();
menuEnabled = false;
}
Sound Effects
Now that we have the music all set up let’s move onto the Sound Effects.
New Level and Gravity Pulse
The first sound bite is when our user achieves a new level (currently set to every 2,000 points). The second scenario is if they achieve a gravity pulse (previously covered here). Both of these scenarios are handled in our updateScore function which gets called during our main game loop (more on game loops to come).
//update player score
function updateScore(points) {
score += points;
scoreGravity += points;
txtScore.innerHTML = " Score: " + score;
if (scoreGravity === GRAVITY_WAVE_PTS_REQ) {
accelerometer.addEventListener("shaken", onShakenAccel);
txtScore.innerHTML = " > SHAKE THAT SCREEN <";
scoreGravity = 0;
SoundJS.play("pulse", SoundJS.INTERRUPT_ANY);
}
//new level
lvlNextPts = (lvlCurrent + 1) * LEVEL_PTS_REQ;
if (score >= lvlNextPts) {
lvlCurrent++;
txtLevel.innerHTML = "Level: " + lvlCurrent;
lvlDifficulty = LEVEL_SPEED_INCREASE * lvlCurrent;
SoundJS.play("newlevel",SoundJS.INTERUPT_ANY);
}
}
Lasers Beams!
All of the logic for the laser effects happens when I handle destroyed ships that have been correctly “tapped on” (more on handling Touch events to come). I run through the laser sound array we previously created and pick a random sound. This gives us the ability to have three different laser sounds when we are blowing up ships on the screen!
function destroyShip(ship) {
var r = randomAccel(0, 2);
var laserSound = lasers[r];
SoundJS.play(laserSound, SoundJS.INTERRUPT_ANY);
//TODO: Animation Explosion and better sound
var explosion = new Image();
explosion.onload = function () {
ctx.drawImage(explosion, ship.x, ship.y);
}
explosion.src = "/images/explosion.png";
ship.img = explosion;
ship.destroyed = true;
updateScore(POINTS_SHIPHIT);
return ship;
}
For those curious here is the randomizer function I used to pick a random number as well:
//Random acceleration speed
function randomAccel(a, b) {
return (Math.floor(Math.random() * (1 + b - a))) + a;
}
One of the things I noticed when having many sound effects on screen was that they sometimes would cut off each other. Remember that users can use all 10 fingers on a touch device so we could have 10 explosions at once! This is where SoundJS handles things very well. You may have noticed the INTERRUPT_ANY param being passed in. This is where you can tell SoundJS how to handle currently playing sounds of the same exact stream. You can also created multiple instances of the same sound effect. If you are still having issues with choppy sounds the SoundJS Documentation has some suggestions on using setTimeout to buffer your sounds.
I wrote a little function that implements this which I’m not currently using but may need at some point in the future. I’ve included it here in case you run into any sound clipping issues and want to give it a try.
//Play Sound Effect
function playSound(sound) {
setTimeout(function () {
SoundJS.play(sound, SoundJS.INTERUPT_NONE, 1, false);
}, 100);
}
Conclusion
I hope this post has given you an idea of how easy it is to include Music and Sound Effects in your Metro Style Apps using the built in HTML5 Audio tags and existing JavaScript Libraries. If you are currently working on a Windows 8 app and want to get into the Windows Store I would love to hear about it!
You may also want to check out my previous Windows 8 Metro Style Development Tips:
- Accessing the Accelerometer in a Windows 8 Metro Style App using HTML and JavaScript
- Accessing the Camera in a Windows 8 Metro Style App using HTML and JavaScript
- Using KnockoutJS in Windows 8 Metro Style Apps
- Illegal characters in path when deploying a Metro Style App
- Connecting to WCF RIA Services in a Windows 8 Metro Style App using Upshot.js and Knockout.js
- Help! Visual Studio 11 Beta Dark Theme incorrectly using White Background
- Adding Touch support to a Windows 8 Metro Style App using HTML and JavaScript
- Defining Layout in a Windows 8 Metro Style App using CSS3, HTML and JavaScript
- Handling Fullscreen, Snapped and Filled states in Windows 8 Metro Style apps using CSS3 and JavaScript
- “The application could not be started.” error when debugging a Windows 8 Metro Style App
- Migrating a Windows 8 Metro Style App from Consumer Preview to Release Preview