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.

Sounds and JavaScript - sweet!

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: