Time for action – creating AudioSequencer

Let's create our AudioSequencer object in a file called audioSequencer.js. We'll start by defining a few variables:

function AudioSequencer()
{
    var _events = [],
        _playbackRate = 1,
        _playing = false,
        eventHandler = undefined,
        timeoutID = 0;

First we define an _events array to hold all of the music events to play. The _playbackRate variable controls how fast the song plays. A value of 1 will be at normal speed, less than 1 slower, and more than 1 faster. The _playing variable is set to true while a song is playing. eventHandler will be set to a function that gets called when an event is fired, and timeoutID will contain the handle returned from setTimeout() in case the user stops the game and we need to cancel the timeout.

Now let's define some public property methods. The first is events(). It is used to get or set the _events array:

    this.events = function(newEvents)
    {
        if (newEvents) {
            _events = newEvents;
            return this;
        }
        return _events;
    };

The next is playbackRate(). It is used to get or set _playbackRate:

    this.playbackRate = function(newRate)
    {
        if (newRate) {
            _playbackRate = newRate;
            return this;
        }
        return _playbackRate;
    };

Finally we have isPlaying(), which is used to determine if a song is currently playing:

    this.isPlaying = function()
    {
        return _playing;
    };

Now we will code the public startPlayback() method. This method takes two parameters; the event handler function and optionally the starting position, which is an index into the _events array:

    this.startPlayback = function(callback, startPos)
    {
        startPos = startPos || 0;
        
        if (!_playing && _events.length > 0)
        {
            _playing = true;
            eventHandler = callback;
            playEvent(startPos);
            return true;
        }
        return false;
    };

The first thing we do is default the startPos parameter to 0, if it was not provided. Next we check that a song isn't already playing, and make sure we actually have some events to play. If so we set the _playing flag to true, store the event handler reference, and then call playEvent() for the first event. We return true if playback was successfully started.

Now let's write the playEvent() method. It takes one parameter, the index of the next event to fire:

    function playEvent(index)
    {
        var event = _events[index];
        eventHandler(event.event, event.note, index);

        index++;
        if (index < _events.length)
        {
            timeoutID = setTimeout(function()
            {
                playEvent(index);
            },
            _events[index].deltaTime * (1 / _playbackRate));
        }
        else _playing = false; // all done
    }

The first thing we do is get the event at the specified index in the _events array. Then we immediately call the event handler's callback function that was provided in the startPlayback() method, passing it the event code, the note to play, and the event index.

Next we increment the index to get the next event. If there is another event we call setTimeout() to wait for the amount of time specified in the event's deltaTime field before calling playEvent(), again passing it the index of the next event. We compute the amount of time to wait by multiplying deltaTime by the inverse of the playback rate. For example, if the playback rate is 0.5 then the wait time will be 1 , 0.5 or 2 times the normal rate. This loop continues in this fashion until there are no more events to play.

The last thing we need is a public stopPlayback() method. This method is called to stop the event loop, and therefore stop the playback of the audio events:

    this.stopPlayback = function()
    {
        if (_playing)
        {
            _playing = false;
            if (timeoutID) clearTimeout(timeoutID);
            eventHandler = undefined;
        }
    };

First we check the _playing flag to make sure a song is actually playing. If so, we set the flag to false, and then we call clearTimeout() to stop the timeout. This will stop playEvent() from being called again, which will stop the playback loop.

The last thing we need is to define the playback event codes, so we don't have to remember the event code numbers. We will define a pseudo enumeration using an object on AudioSequencer called eventCodes:

AudioSequencer.eventCodes =
{
    noteOn: 1,
    noteOff: 2,
    cuePoint: 3,
    endOfTrack: 4
};

What just happened?

We created an audio sequencer object that takes an array of music events, similar to MIDI events, and calls them at the correct time using the setTimeout() function. When an event is fired it calls the event handler function, passed in by the game panel.

Note

Although we have written this code to play music, you could use the same technique anywhere you need things to happen at predetermined intervals.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.243.184