Retro Game Music using Web Audio and Band.js

by Brian Rinaldi on September 9, 2013

The modern web is always changing, and this article is more than two years old.

zelda_band_header

By Brian Rinaldi

Being a proper geek, I have a (mostly) healthy love for video games and being old(ish), I have a strong streak of nostalgia. Thankfully, classic 8-bit games are are apparently back in style and even new games are aping the 8-bit style. Music plays a big role in the overall feel of a game, and 8-bit games have a distinctive sound. A new library, Band.js by Cody Lundquist allows you to recreate that sound using JavaScript and the Web Audio API.

In this article, I’ll show you how to use Band.js to create game music using it’s JavaScript API. We’ll walk through an example where I have recreated the classic Overworld theme from the original Legend of Zelda on NES (reinterpreted, a bit to be truthful as it isn’t identical to the original). We’ll also see how you can format the music as JSON and load it into Band.js. Finally, we’ll look at an alternative JSON format I created that mightmake it slightly easier to convert complex songs into a format that can be used by the library.

What is Band.js?

Band.js, created by Cody Lundquist, let’s you create music using 8-bit oscillation sounds with sine, square, sawtooth or triangle wave formats using Web Audio. You may have already heard about 8bit.js which is by the same author and has a very similar API. Band.js offers some important additional options that 8bit.js didn’t and is intended to take its place. If you’ve already written something using 8bit.js, most of your code can easily transfer, however Band.js has reversed the order of note and pitch in it’s method calls and JSON, which can be a time-consuming change.

It’s important to keep in mind that Web Audio support is still slightly limited. While it works on most desktop browsers (yes, not IE yet), it’s support on mobile is limited. I should note that while it does work on iOS, the examples here didn’t run well on that platform.

web-audio-supprt

Essentially, you create a composition using Band.js, setting it’s time signature, tempo and global volume. For example, in the Overworld theme example we’ll be discussing throughout this article, the song options I supplied are:

var music = new BandJS();
music.setTimeSignature(4,4);
music.setTempo(116);

This instantiates a new instance of BandJS and then sets it for a 4/4 time signature and a tempo of 116 beats per minute. I didn’t supply a volume, leaving it at the default which is the maximum value of 100.

Next you create instruments with options for the volume per instrument. In many examples, this is shown as a left hand instrument and a right hand instrument, which essentially represent the left and right hand portions of a piano score. However, I don’t think there is a limit on the number of instruments you can create. For example:

var lefthand = music.createInstrument('square'),
        righthand = music.createInstrument('square');

One of the core differences between 8bit.js and Band.js is the ability to choose an instrument pack for the sounds. By default, it uses the oscillators pack which is the same as the sounds that were included in 8bit.js: sine, square, sawtooth or triangle. However, Band.js also includes a noises instrument pack and you can create other custom packs of your own. For an example of the noises pack, you listen to the Super Mario Theme created by Cody which uses it for the drum. There are also rhythm and tuning packs for the more musically inclined than myself, which you can use when you instantiate Band.js.

Now that our instruments are created, let’s begin creating the music.

Transcribing Music into Band.js

Obviously, in order to convert any song into Band.js it helps to have a score written out – simply messing with adding notes in JavaScript isn’t likely to get a very listenable song. In my case, since I am not creating an original song, I was able to locate a score for Overworld arranged by Rachel Bell (PDF).

The API for creating notes and rests is pretty simple. To create a note you use the note() method and supply the following arguments:

  • note – this is the duration, as in quarter note, half note, etc. BandJS supports fifteen different note values including triplets and dots. In the case of the song I was transcribing, there was a dotted triplet on the score which Band.js did not have, but every other option was accounted for.
  • pitch – for the musically ignorant, like myself, this is the key being played, as in C,D,E,F,G,A and B with sharps or flats where appropriate. You will need to supply the octave as well. So for instance, a C sharp in the second octave would be C#2 and a B flat in the third octave would be Bb3.
  • tie – whether the current note is tied to the subsequent note (as in, it’s duration is extended across both notes)

The rest() method simply requires the note as pitch and tie don’t apply. Let’s look at an example, taken from the Overworld theme:

righthand.note('quarter','D4,Bb4')
        .note('tripletEighth','D4,F4')
        .note('tripletEighth','D4')
        .note('tripletEighth','C4')
        .note('tripletEighth','D4', true)
        .note('tripletSixteenth','D4')
        .note('tripletEighth','D4,Bb4')
        .rest('tripletSixteenth')
        .note('sixteenth','D4,Bb4')
        .note('sixteenth','Eb4,C5')
        .note('sixteenth','F4,D5')
        .note('sixteenth','G4,Eb5');

The variable righthand in this case is an instance of a Band.js instrument. The API is pretty self explanatory when looking at it I think. The one practice that helped was to separate out each measure as otherwise it was hard to keep track of where you were at any given time.

Once the music is created, we do things like start it, pause it and stop it. First, we let Band.js know that the music is done:

lefthand.finish();
righthand.finish();
music.end();

Once that is done, we can play using music.play(); (where, again, music is the variable containing my instance of BandJS), pause using music.pause(); and stop using music.stop();. The difference between pause and stop is that on a stop the play head moves back to the beginning. Pretty straightforward I think.

You can see all of this pulled together in the following sample app below (note: I apologize in advance for the ugly design).

[iajsfiddle fiddle=”gqPyj” height=”300px” width=”100%” show=”result,js,html,css” skin=”default”]

Loading via JSON

If you took a look at the full JavaScript example code for all 24 measures of the Overworld theme, you may have noticed that it comes in well over 400 lines of JavaScript code. I wouldn’t blame you if you were thinking, “There is no way I am going to write entire songs this way.” This is compounded if you had multiple songs for a game, for instance.

Thankfully, Band.js supports a JSON song format that is a bit cleaner and easier to write than the straight JavaScript format but has the benefit that you can more easily separate the song code from the code necessary to play songs.

First let’s look at the song format. Within the JSON format, you can define all the properties of the song including the time signature, tempo and so forth:

timeSignature: [4, 4],
tempo: 120,
instruments: {
    rightHand: {
        name: 'square',
        pack: 'oscillators'
    },
    leftHand: {
        name: 'square',
        pack: 'oscillators'
    },
}

You’ll notice that I am using a similar rightHand/leftHand structure as in the JavaScript example. The notes are assigned to a property “notes” containing the same righHand/leftHand structure with all the notes defined for each as an array. For example, here’s the first measure for right hand:

rightHand: [ 'half|D4,Bb4|true',
'dottedEighth|D4,Bb4',
'sixteenth|D4,Bb4',
'tripletEighth|D4,Bb4',
'tripletEighth|D4,Bb4',
'tripletEighth|D4,Bb4'

One thing to note is that the rightHand and leftHand (or any other instruments) must have their notation laid out in its entirety, so it isn’t as easy to define each measure for all instruments separately, making it a little harder to read the code in my opinion.

To load and play the song, you simply need to provide the JSON to Band.js (mine is in a variable I named musicjson because I lack the creativity for a better variable name):

var music = new BandJS();
music.load(musicjson);
music.end();

Once loaded, you can play/pause/stop the same as before. Here’s the same sample app as above but using the load() method rather than inline JavaScript.

Note: to avoid cross site scripting issues, I cheated a little on the JSON, giving it a .js extension and simply assigning it to a variable. I didn’t want to use JSFiddle’s Request.json simply because the length of this JSON object would clutter the JavaScript. The point here is more the format than the actual loading of the JSON.

[iajsfiddle fiddle=”aserz” height=”300px” width=”100%” show=”js,result” skin=”default”]

An Alternative JSON Format

First let me say that this isn’t really meant as a criticism – rather a suggestion. I wasn’t really digging the JSON format. It may be my musical inexpertise but I wanted something that made it easier for me to see measures for each instrument. Otherwise, I would find it hard to catch and locate my mistakes and often had one too many notes or the wrong length in a copy/paste mistake somewhere.

My slight reformat allowed you to subdivide each measure. It also got rid of the pipe delimited list of arguments, which I will say I am not a huge fan of. The format is a little more verbose but I find it much easier to read and find problems, personally. Let’s see a snippet of a couple measures from the refomatted notes portion.

[
    ['quarter','D4,Bb4'],
    ['tripletEighth','D4,F4'],
    ['tripletEighth','D4'],
    ['tripletEighth','C4'],
    ['tripletEighth','D4','true'],
    ['tripletSixteenth','D4'],
    ['tripletEighth','D4,Bb4'],
    ['tripletSixteenth'],
    ['sixteenth','D4,Bb4'],
    ['sixteenth','Eb4,C5'],
    ['sixteenth','F4,D5'],
    ['sixteenth','G4,Eb5']
],
[
    ['eighth','Ab4,F5'],
    ['eighth','Bb4'],
    ['sixteenth','Bb4'],
    ['sixteenth','C5'],
    ['sixteenth','D5'],
    ['sixteenth','Eb5'],
    ['eighth','F5'],
    ['eighth','F5'],
    ['tripletEighth','Ab4,F5'],
    ['tripletEighth','Bb4,Gb5'],
    ['tripletEighth','C5,Ab5']
]

While not dependent on using this version, I would love to see a way for Band.js to offer a method that might analyze your notes and flag potential places where your measure doesn’t add up to the proper number of beats. This was an issue I encountered myself many times. If measures were clearly readable and a method like this existed, it would be easy to locate and correct (for the record, my original plan was to write this method and contribute it but I simply ran out of time before publishing the article).

The below example uses the new format by overwriting the load method on Band.js. Note that as before I am cheating a little bit in how I load the JSON to avoid writing it all out in the JavaScript file.

[iajsfiddle fiddle=”cSGwx” height=”300px” width=”100%” show=”js,result” skin=”default”]

Where to Go From Here

Whether just for fun or for a game you are developing Band.js offers an easy way to create classic 8-bit music. Cody has been continuing to expand the capabilities of the library. For example. he’s mentioned that he’ll planning to add in validation of measures as I’ve discussed earlier in this article. Also, recently he’ve been working on buffering notes rather than loading everything at once which means that, even if you have a song that is hours long, it won’t max out your memory. Band.js definitely is already turning into a powerful music creation tool.

© 2017 Modern Web & our authors. All rights reserved.