Creating game for android using JavaScript #4 | Sounds, Web Audio Api

It would be hard to imagine a game without a sound. Fortunately there is powerful Web Audio Api that will help us to include sound to our game.

|

Read also:

Creating game for android using JavaScript #1 | How to start

Creating game for android using JavaScript #2 | Drawing game objects

Creating game for Android using JavaScript #3 | Events and gameplay

---

Sound

It would be hard to imagine a game without a sound. Fortunately there is powerful Web Audio Api that will help us to include sound to our game. The Web Audio API provides a powerful system for controlling audio on the Web. Using it we are able to choose audio source, add special effects to it and much more.

Create new Sound class in sound.js file.

Creating an audio context

First, we need to create an instance of AudioContext to build an audio graph upon. Sound class constructor will be good place for it.

this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();

 It’s important to provide a prefixed version for Webkit/Blink browsers, and a non-prefixed version for Firefox (desktop/mobile/OS).

Now we are able to choose audio source. Web Audio Api allows to choose from various sources:

  • Generated directly by JavaScript by an audio node such as an oscillator.
  • Created from raw PCM data: the audio context has methods to decode supported audio formats;
  • Taken from HTML media elements such as <video> or <audio>
  • Taken directly from a WebRTC MediaStream such as from a webcam or microphone.

OscillatorNode

I’ve chosen the first option, to generate sound programmatically. OscillatorNode interface is an audio-processing module that can create a tone with given frequency. It should be similar to old arcade games sound. For this, we need an oscillator to provide a simple tone for our source, and a gain node to control sound volume.

Creating gainNode to control sound volume:

this.gainNode = this.audioCtx.createGain();        
this.gainNode.gain.value = 0.5;

AudioContext.destination indicates to the default output mechanism of device (usually device speakers). It’s time to connect the oscillator, gain node and destination together.

this.gainNode.connect(this.audioCtx.destination);
let oscillator = this.audioCtx.createOscillator();
oscillator.connect(this.gainNode);

Now we are able to create tone with given frequency.

oscillator.frequency.value = 400; //HZ frequency
oscillator.start();
oscillators.stop(this.audioCtx.currentTime + 1);

What is really important oscillator can be started only once. After calling stop() method oscillator is destroyed. Stop method accepts time after oscillator stops “singing” as parameter. So we can create sounds with a specified duration.  Another important information: time started counting just after audioCtx is created so we need to add audioCtx.currentTime to make it works as expected. We are also not able to change frequency value after oscillator started.

Knowing this obstacles I’ve created simple methods that allow me to create multiple sounds one after another.

createSound (notes, times, type = 'sawtooth') {
        let oscillators = [];
        notes.forEach((note, index) => {
            let oscillator = this.audioCtx.createOscillator();
            oscillator.connect(this.gainNode);
            oscillator.type = type;
            oscillator.frequency.value = note;
            oscillator.onended = this.playNote.bind(this, index + 1, oscillators, times);
            oscillators.push(oscillator);
        });
        this.playNote (0, oscillators, times);
    }
 
    playNote (index, oscillators, times) {
        if(index < oscillators.length ) {
            oscillators[index].start();
            oscillators[index].stop(this.audioCtx.currentTime + times[index]);
        }
    }

This method uses onended callback method to play next oscillator in the queue. createSound method accepts as parameters arrays of frequencies and duration times. Also oscillator type can be set. It is used to define sound timbre. It can be set to: ‘sine', 'wave', 'square', 'sawtooth', 'triangle' or 'custom'. We are using 'sawtooth' as default because it's similar to old games sound timbre. Let's play music! I want to play some tunes after game is over.

gameOver () {
        let notes = [300, 150,  100,]; // hertz
        let times = [0.3, 0.3,  0.4]; // seconds
        this.createSound (notes, times);

        this.createSound ([400, 250, 300], [0.3, 0.3, 0.4], 'triangle');
}

What I have done here, is the possibility to play two paths of different tones at the same time! Moreover, this paths have different sound timbre! So if you are talented musician it's time to compose some cool stuff!

At the end now we can play our beautiful “music” after our fearless Hero hits the obstacle or the ground. We have to create Sound object in Game object constructor.

this.sound = new Sound();

And after game is over, in gameOver method of Game class, simple call:

What a piece of great music! Or not… ;)

To be continued...

Józef Kała

Senior Java / JEE Software Engineer. In j-Labs from April 2016. He currently works for Sabre in the Commercial Analytics project. Apart from the purely commercial programming, privately interested in the market of mobile games and applications.

jozef.kala@j-labs.pl

Did you like this article?

Creating game for android using JavaScript #4 | Sounds, Web Audio Api