Home Articles FAQs XREF Games Software Instant Books BBS About FOLDOC RFCs Feedback Sitemap
irt.Org

Related items

Kick some booty with invisible Flash!

JavaScripting Essentials

JavaScript Bookmarklets

Why bother with JavaScript?

JavaScript Games #2 - Solitaire

JavaScript Beginners Start Here

Keeping Count of Downloads

JavaScript Games

Online JavaScript Resources

Writing a midi hifi system in JavaScript

You are here: irt.org | Articles | JavaScript | Miscellaneous | Writing a midi hifi system in JavaScript [ previous next ]

Published on: Friday 29th May 1998 By: Martin Webb

Introduction

Musical accompaniment for Web pages becomes a little easier with the use of LiveAudio methods (as described by Netscape in the Netscape JavaScript Reference Manual) to create a JavaScript midi hifi system that will enable midi or wav files to be played as if they were on a CD player. The functionality includes complete play, stop and pause control, and single play, cycle play and random play. You can even monitor time played, time remaining, track time and total time, just like the CD player in a home sound system.

Playing music

The following embed tag will play a midi or wav file as soon as it's loaded:

<embed src="fugue.mid" hidden="true">

Controlling music

The following allows you to control when the midi or wav file plays:

<script language="JavaScript"><!--
function playSound()  { document.firstSound.play(false); }
function pauseSound() { document.firstSound.pause(); }
function stopSound()  { document.firstSound.stop(); }
//--></script>

<embed src="fugue.mid" hidden="true" autostart="false" loop="false" 
name="firstSound" mastersound>

<a href="javascript:playSound()">Play the sound now!</a> -
<a href="javascript:pauseSound()">Pause/Restart the sound</a> -
<a href="javascript:stopSound()">Stop the sound</A>

Creating a play list

The JavaScript code below uses the tried and trusted object array to hold a list of the tracks on the play list. Each Tune object in the myTune[] array has three properties: tuneName (the filename of the tune), time (the time in seconds that the tune is expected to last) and size (size in Kb of the tune file).

The item variable keeps a count of how many tunes there are and the total variable holds the total number of seconds for the all the tunes; i.e., the time/length of the play list.

The setTune() function is used to add each Tune object to the myTune[] object array.

<script language="JavaScript"><!--
function Tune(tuneName,time,size) {
    this.tuneName = tuneName;
    this.time = time;
    this.size = size;
}

function setTune(tuneName,time,size) {
    size = (size < 10) ? '0' + size : size;
    myTune[item++] = new Tune(tuneName,time,size);
    total += time;
}

var item = 0;
var total = 0;
var myTune = new Array();

setTune('babybumb.mid', 17, 4);
setTune('fugue.mid',    82, 5);
setTune('copland1.mid', 41, 6);
setTune('india.mid',    61, 6);
setTune('vaudevil.mid', 14, 9);
//--></script>

Displaying the midi hifi

The following JavaScript code and HTML displays the midi hifi. The first part of the tuner form is output to the document using JavaScript. This enables each Tune object with the myTune[] object array to be output as an option within the tunes select array.

The remainder of the form -- i.e., the play, stop and start buttons, along with the Single Play, Cycle Play and Random Play play options, as well as the Time Played, Time Remaining, Track Time and Total Time time options and seconds digital clock -- are all output using HTML.

<body bgcolor="#ffffff" leftmargin="0" topmargin="0" 
onLoad="resetstartTime();updateStatus()">

<script language="JavaScript"><!--
var tunePlaying = 0;
var watching = false;

var ready = false;
var playing = false;
var paused = false;
var single = true;
var cycle = false;
var wasPlaying = false;

var random = false;
var repeat = false;
var selected = false;

var timeoutID = 0;
var milliseconds = 0;
var seconds = 0;
var minutes = 0;
var startTime = 0;
var pauseTime = 0;
var resumeTime = 0;
var pausedMilliseconds = 0;
var tracktime = '';
var trackleft = '';
var tracktotal = '';
var totaltime = formatTime(total);
var start = 0;

var now = new Date();

document.write('<center><table><form name="tuner" onSubmit="return false;">');

document.write('<tr><td align=center><select name="tunes" onChange="if (playing) hifiPlay(document.tuner.tunes.selectedIndex)">');
document.write('<option selected>' + formatTime(myTune[0].time) + ' ' + myTune[0].size + 'Kb' + ' ' + myTune[0].tuneName);
for (i=1; i<item; i++)
   document.write('<option>' + formatTime(myTune[i].time) + ' ' + myTune[i].size + 'Kb' + ' ' + myTune[i].tuneName);
document.write('</select></td></tr>');
//--></script>

<tr><td align="center">

<input type="button" value=" > " onClick="hifiStart(document.tuner.tunes.selectedIndex)">
<input type="button" value=" # " onClick="hifiStop()">
<input type="button" value=" || " onClick="hifiPause()">

<select name="play">
<option selected>Single Play<option>Cycle Play<option>Random Play
</select>

</td></tr><tr><td align="center">

<select name="time" onChange="updateStatus()">
<option selected>Time Played<option>Time Remaining<option>Track Time<option>Total Time
</select>

<input type="text" value="00:00" name="seconds" size="5">

</td></tr></form></table></center>

</body>

The totaltime variable is set to the value returned by the formatTime() function, which is passed as its argument the value of total, i.e., the total time of the Play List in seconds.

When the tunes select array is changed, the selects onChange event handler invokes the hifi() function only if playing passing as its argument the selectedIndex of the tuner forms tunes select array.

When the document has fully loaded, the BODY tags onLoad event handler invokes the resetstartTime() and updateStatus() functions.

When the play ('>') button is pressed, the buttons onClick event handler invokes the hifiStart() function, passing as its argument the selectedIndex of the tuner forms tunes select array. When the stop ('#') button is pressed, the buttons onClick event handler invokes the hifiStop() function. When the pause ('||') button is pressed, the buttons onClick event handler invokes the hifiPause() function. When the time select array is changed, the selects onChange event handler invokes the updateStatus() function.

Controlling the midi hifi

The hifiStart() function receives the number (no) of the selected tune. It first updates the value of the global variable random with the selected property of the last entry within the play select array. If random play has been selected, then a pseudo-random number (rnd) is generated within the range of the number of item tunes. This is then compared against the current tunePlaying; if it's the same, then the rnd number is increased by 1. The hifiPlay() function is invoked either with the rnd or no number.

<script language="JavaScript">    
function hifiStart(no) {
    random = document.tuner.play[2].selected;
    if (random) {
        var rnd = Math.floor((Math.random() * 100000) % item);
        if (rnd == tunePlaying) rnd++;
        hifiPlay(rnd);
    }
    else
        hifiPlay(no);
}

The hifiPlay() function accepts the tune number to play (no) and sets the value of the global variable tunePlaying to this value. If tunePlaying is greater than the number of tunes (i.e., item), then tunePlaying is set to the first tune.

The mysound play() method is then used to play the tuneName property of the tunePlaying tune in the myTune[] object array.

The tunePlaying entry within the tunes options array is then selected; i.e., the tune entry is highlighted within the select box. This ensures that you can see what tune is playing.

The resetstartTime(), startTimer() and updateStatus() functions are then all invoked.

function hifiPlay(no) {
    tunePlaying = no;
    if (tunePlaying >= item) tunePlaying = 0;
    document.mySound.play(false,myTune[tunePlaying].tuneName);
    document.tuner.tunes.options[tunePlaying].selected = true;
    resetstartTime();
    startTimer();
    updateStatus();
}

The hifiStop() function uses the mySound stop() method to stop the current tune from playing. The resetstartTime() function is invoked. The global variable playing is then set to the value returned by the mysound IsPlaying() method. This ensures that the correct playing status is set. The stopTimer() and updateStatus() functions are invoked.

function hifiStop() {
    document.mySound.stop();
    resetstartTime();
    playing = document.mySound.IsPlaying();
    stopTimer();
    updateStatus();
}

The hifiPause() functions uses the mySound pause() method to pause/resume the tune playing. Because it toggles between pause and resume, we then check whether the tune is actually paused by setting the value of the global variable paused to the mySound IsPaused() method.

If the tune is paused, then we invoke the stopTimer() function and set the value of the global variable pauseTime to the current time in milliseconds. The updateStatus() is also invoked.

If the tune is not paused, then it's just been resumed, so we set the global variable resumeTime to the current time in milliseconds. We use the current value of pausedMilliseconds, resumeTime and pauseTime to set the global variable pausedMilliseconds to the number of milliseconds that the tune was paused for. It is possible that the tune can be paused more than once. The startTimer() and updateStatus() functions are then invoked.

function hifiPause() {
    document.mySound.pause();
    paused = document.mySound.IsPaused();
    if (paused) {
        stopTimer();
        now = new Date();
        pauseTime = Date.UTC(now.getYear(),now.getMonth(),now.getDate(),now.getHours(),now.getMinutes(),now.getSeconds());
        updateStatus();
    }
    else {
        now = new Date();
        resumeTime = Date.UTC(now.getYear(),now.getMonth(),now.getDate(),now.getHours(),now.getMinutes(),now.getSeconds());
        pausedMilliseconds = pausedMilliseconds + resumeTime - pauseTime;
        startTimer();
        updateStatus();
    }
}

The hifiEnd() function first stops the current tune by using the mySound stop() method. It then sets the value of the global variable playing to the value returned by the mySound IsPlaying() method. The global variable wasPlaying is also set to this value. The resetstartTime(), stopTimer() and updateStatus() functions are then invoked.

If the play method is either cycle or random then another tune is played by invoking the hifiStart() function passing as its argument the incremented global variable tunePlaying.

function hifiEnd() {
    document.mySound.stop();
    playing = document.mySound.IsPlaying();
    wasPlaying = playing;
    resetstartTime();
    stopTimer();
    updateStatus();
    if (cycle || random) hifiStart(++tunePlaying);
}

The startTimer() function creates a timer referenced by the global variable timeoutID to invoke the keeWatching() function after 1000 milliseconds. The global variable watching is set to true.

function startTimer() {
    timeoutID = setTimeout('keepWatching()', 1000);
    watching = true;
}

The stopTimer() clears the timer referenced by the global variable timeoutID, and then sets the global variable watching to false.

function stopTimer() {
    clearTimeout(timeoutID);
    watching = false;
}

The keepWatching() function invokes the updateStatus() and then if the global variable playing is true it sets a new timer referenced by the global variable timeoutID to invoke the keepWatching() function after 1000 milliseconds; i.e., it keeps watching whilst the current tune is playing.

function keepWatching() {
    updateStatus();
    if (playing)
        timeoutID = setTimeout('keepWatching()', 1000);
}

The resetstartTime() function resets certain global variables used throughout the rest of the JavaScript code.

function resetstartTime() {
    now = new Date();
    startTime = Date.UTC(now.getYear(),now.getMonth(),now.getDate(),now.getHours(),now.getMinutes(),now.getSeconds());
     pauseTime = 0;
     resumeTime = 0;
     milliseconds = 0;
     seconds = 0;
    pausedMilliseconds = 0;
}

The updateStatus() function first sets the value of wasPlaying to that of playing. It then updates the global variables: the value of playing to the mySound IsPlaying(), the value of single, cycle and random play to the selected option with the play[] select array.

If the tune is playing and not paused, then the number of milliseconds that the tune has been playing since the startTime global variable is calculated. This is then used to calculate the total number of seconds that the tune has been playing, taking into consideration the number of pausedMilliseconds. The tracktime global variable is then set to the number of seconds that the tune has played. The trackleft global variable is then set to the time property of the Tune object currently playing (i.e., tunePlaying) within the myTune object array, minus the number of seconds the tune has been playing. The tracktotal is set to the time property of the same Tune object. Each of these three time values is formatted by a call to the formatTime() function.

Depending on which timer is being request to display -- i.e., the selected option within the time select array -- and whether the time displayed in the seconds form field has since changed, the seconds form field is then updated with the required time display.

Finally, and most important, if the number of seconds the tune has been playing is greater than the Tune objects time property, or if the tune was playing, but is now not playing, then the hififEnd() function is invoked. In other words, after the expected duration of the tune has been reached, the tune is killed. This then enables us to start another tune if need be.

function updateStatus() {
    wasPlaying = playing;

    playing = document.mySound.IsPlaying();
    single = document.tuner.play[0].selected;
    cycle = document.tuner.play[1].selected;
    random = document.tuner.play[2].selected;

    if (playing && !paused) {
        now = new Date();
        milliseconds = Date.UTC(now.getYear(),now.getMonth(),now.getDate(),now.getHours(),now.getMinutes(),now.getSeconds()) - startTime;
        seconds = (milliseconds - pausedMilliseconds) / 1000 - 1;
        if (seconds == -1) seconds = 0;
    }

    tracktime  = formatTime(seconds);
    trackleft  = formatTime(myTune[tunePlaying].time - seconds);
    tracktotal = formatTime(myTune[tunePlaying].time);

    if (document.tuner.time[0].selected && document.tuner.seconds.value != tracktime)
        document.tuner.seconds.value = tracktime;

    if (document.tuner.time[1].selected && document.tuner.seconds.value != trackleft)
        document.tuner.seconds.value = trackleft;

    if (document.tuner.time[2].selected && document.tuner.seconds.value != tracktotal)
        document.tuner.seconds.value = tracktotal;

    if (document.tuner.time[3].selected && document.tuner.seconds.value != totaltime)
        document.tuner.seconds.value = totaltime;

    if (seconds > myTune[tunePlaying].time || (wasPlaying && !playing))
        hifiEnd();
}

The formatTime() functions formats the time passed is seconds using the format HH:MM and then returns the formatted time to whatever invoked it.

function formatTime(time) {
    var timeSeconds = time % 60;
    var timeMinutes = Math.floor(time / 60);
    return ((timeMinutes < 10) ? '0' + timeMinutes : timeMinutes) + ':' + ((timeSeconds < 10) ? '0' + timeSeconds : timeSeconds);
}
//--></script>

Working example

Why not try out the midi hifi working example.

Related items

Kick some booty with invisible Flash!

JavaScripting Essentials

JavaScript Bookmarklets

Why bother with JavaScript?

JavaScript Games #2 - Solitaire

JavaScript Beginners Start Here

Keeping Count of Downloads

JavaScript Games

Online JavaScript Resources

Feedback on 'Writing a midi hifi system in JavaScript'

©2018 Martin Webb