///////////////////////////////////////////////////////////////////////////////
// 
// File: VMPlayer.cs
// 
// Description: 
//    This is a virtual music player used for playing music in the game world.
//    Features include the ability to create multiple playlists/track, save/load
//    them to/from disk, fading, cross fading, random playing, event triggering,
//    and much more.
///////////////////////////////////////////////////////////////////////////////
//
// Copyright Notice:
//    Copyright 2006 Robert Geiman.  All rights reserved.  Permission to use
//    this file in personal, government, or commercial use is granted as long
//    as this copyright notice is not removed.
///////////////////////////////////////////////////////////////////////////////

// Tweakable settings
$VMPlayerFadeIntervalMS = 100;         // Number of MS between each interval of fade in/out
$VMPlayerCheckSongPosIntervalMS = 100; // Number of MS between checking the current play song for events/track changes


// Non-tweakable settings!
new AudioDescription(VMPlayerStreamingAudioDescription1) { isStreaming = true; is3D = false; type = 1; isLooping = false; };
new AudioDescription(VMPlayerStreamingAudioDescription2) { isStreaming = true; is3D = false; type = 2; isLooping = false; };
new AudioDescription(VMPlayerStreamingAudioDescription3) { isStreaming = true; is3D = false; type = 3; isLooping = false; };
new AudioDescription(VMPlayerStreamingAudioDescription4) { isStreaming = true; is3D = false; type = 4; isLooping = false; };
new AudioDescription(VMPlayerStreamingAudioDescription5) { isStreaming = true; is3D = false; type = 5; isLooping = false; };
new AudioDescription(VMPlayerStreamingAudioDescription6) { isStreaming = true; is3D = false; type = 6; isLooping = false; };
new AudioDescription(VMPlayerStreamingAudioDescription7) { isStreaming = true; is3D = false; type = 7; isLooping = false; };
new AudioDescription(VMPlayerStreamingAudioDescription8) { isStreaming = true; is3D = false; type = 8; isLooping = false; };

new AudioDescription(VMPlayerAudioDescription1) { isStreaming = false; is3D = false; type = 1; isLooping = false; };
new AudioDescription(VMPlayerAudioDescription2) { isStreaming = false; is3D = false; type = 2; isLooping = false; };
new AudioDescription(VMPlayerAudioDescription3) { isStreaming = false; is3D = false; type = 3; isLooping = false; };
new AudioDescription(VMPlayerAudioDescription4) { isStreaming = false; is3D = false; type = 4; isLooping = false; };
new AudioDescription(VMPlayerAudioDescription5) { isStreaming = false; is3D = false; type = 5; isLooping = false; };
new AudioDescription(VMPlayerAudioDescription6) { isStreaming = false; is3D = false; type = 6; isLooping = false; };
new AudioDescription(VMPlayerAudioDescription7) { isStreaming = false; is3D = false; type = 7; isLooping = false; };
new AudioDescription(VMPlayerAudioDescription8) { isStreaming = false; is3D = false; type = 8; isLooping = false; };

new AudioProfile(VMPlayerStreamingAudioProfile1) { description = "VMPlayerStreamingAudioDescription1"; preload = false; filename = ""; };
new AudioProfile(VMPlayerStreamingAudioProfile2) { description = "VMPlayerStreamingAudioDescription2"; preload = false; filename = ""; };
new AudioProfile(VMPlayerStreamingAudioProfile3) { description = "VMPlayerStreamingAudioDescription3"; preload = false; filename = ""; };
new AudioProfile(VMPlayerStreamingAudioProfile4) { description = "VMPlayerStreamingAudioDescription4"; preload = false; filename = ""; };
new AudioProfile(VMPlayerStreamingAudioProfile5) { description = "VMPlayerStreamingAudioDescription5"; preload = false; filename = ""; };
new AudioProfile(VMPlayerStreamingAudioProfile6) { description = "VMPlayerStreamingAudioDescription6"; preload = false; filename = ""; };
new AudioProfile(VMPlayerStreamingAudioProfile7) { description = "VMPlayerStreamingAudioDescription7"; preload = false; filename = ""; };
new AudioProfile(VMPlayerStreamingAudioProfile8) { description = "VMPlayerStreamingAudioDescription8"; preload = false; filename = ""; };

new AudioProfile(VMPlayerAudioProfile1) { description = "VMPlayerAudioDescription1"; preload = false; filename = ""; };
new AudioProfile(VMPlayerAudioProfile2) { description = "VMPlayerAudioDescription2"; preload = false; filename = ""; };
new AudioProfile(VMPlayerAudioProfile3) { description = "VMPlayerAudioDescription3"; preload = false; filename = ""; };
new AudioProfile(VMPlayerAudioProfile4) { description = "VMPlayerAudioDescription4"; preload = false; filename = ""; };
new AudioProfile(VMPlayerAudioProfile5) { description = "VMPlayerAudioDescription5"; preload = false; filename = ""; };
new AudioProfile(VMPlayerAudioProfile6) { description = "VMPlayerAudioDescription6"; preload = false; filename = ""; };
new AudioProfile(VMPlayerAudioProfile7) { description = "VMPlayerAudioDescription7"; preload = false; filename = ""; };
new AudioProfile(VMPlayerAudioProfile8) { description = "VMPlayerAudioDescription8"; preload = false; filename = ""; };


///////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::onAdd(%this)
{
   %this.PrimaryAudioChannel = 7;
   %this.SecondaryAudioChannel = 8;

   %this.AudioProfiles = new ScriptObject() { class = VMPArray; };

   for (%x = 1; %x <= 8; %x++)
      %this.AudioProfiles.InsertBack(VMPlayerStreamingAudioProfile@%x);

   %this.PrimaryMusicChannel = new ScriptObject()
   {
      AudioProfile = %this.AudioProfiles.GetAt(%this.PrimaryAudioChannel - 1);
      AudioChannel = %this.PrimaryAudioChannel;
      AudioHandle = 0;
   };

   %this.SecondaryMusicChannel = new ScriptObject()
   {
      AudioProfile = %this.AudioProfiles.GetAt(%this.SecondaryAudioChannel - 1);
      AudioChannel = %this.SecondaryAudioChannel;
      AudioHandle = 0;
   };

   %this.Streaming = true;
   %this.Playlists = new ScriptObject() { class = VMPArray; };
   %this.NextTracks = new ScriptObject() { class = VMPArray; };

   %this.CurrentMusicChannel = %this.PrimaryMusicChannel;

   %this.CurrentVolume = $pref::Audio::channelVolume[%this.CurrentMusicChannel.AudioChannel]; // Retrieve the channel volume from the users preferences
   %this.Random = false;
   %this.Repeat = false;
   %this.Fade = false;
   %this.CrossFade = false;
   %this.Debug = false;
   %this.CurrentPlaylist = 0;
   %this.CurrentSong = 0;
   %this.CurrentTrackIndex = -1;
   %this.FadeDuration = 2.0;
   %this.CheckSongPosHandle = 0;
   %this.NextTrackHandle = 0;
   %this.NextTrackDelayMS = 0;
   %this.NextTrackPause = false;
   %this.PreviousPosition = -1;

   %this.FadeOutHandle = 0;
   %this.FadeInHandle = 0;

   %this.CrossFadeOutHandle = 0;
   %this.CrossFadeInHandle = 0;

   %this.NextTrackIndex = 0;
}


///////////////////////////////////////////////////////////////////////////////
// Destructor
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::onRemove(%this)
{
   if (%this.IsPlaying() || %this.IsPaused())
      %this.Stop();
   
   if (isObject(%this.Playlists))
   {
      %this.RemoveAllPlaylists();
      %this.Playlists.delete();
   }

   if (isObject(%this.NextTracks))
   {
      %this.NextTracks.RemoveAll();
      %this.NextTracks.delete();
   }
   
   if (isObject(%this.AudioProfiles))
      %this.AudioProfiles.delete();
      
   if (isObject(%this.PrimaryMusicChannel))
      %this.PrimaryMusicChannel.delete();
      
   if (isObject(%this.SecondaryMusicChannel))
      %this.SecondaryMusicChannel.delete();
}


///////////////////////////////////////////////////////////////////////////////
// SetAudioChannels
//
// Sets the audio channels for VMPlayer to use when playing music.  This
// requires both a primary and secondary audio channel.  The default channels
// used are 7 and 8.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::SetAudioChannels(%this, %primary, %secondary)
{
   if (%primary < 0 || %secondary < 0 || %primary > 8 || %secondary > 8)
   {
      VMPlayerPrintDebugMessage(%this, "Unable to set audio channels, channel number '"@%primary@"' or '"@%secondary@"' out of range");
      return;
   }

   if (%this.IsPlaying() || %this.IsPaused())
      %this.Stop();
      
   %this.PrimaryAudioChannel = %primary;
   %this.SecondaryAudioChannel = %secondary;

   %this.PrimaryMusicChannel.AudioProfile = %this.AudioProfiles.GetAt(%this.PrimaryAudioChannel - 1);
   %this.PrimaryMusicChannel.AudioChannel = %this.PrimaryAudioChannel;

   %this.SecondaryMusicChannel.AudioProfile = %this.AudioProfiles.GetAt(%this.SecondaryAudioChannel - 1);
   %this.SecondaryMusicChannel.AudioChannel = %this.SecondaryAudioChannel;
}


///////////////////////////////////////////////////////////////////////////////
// SetStreaming
//
// Sets audio files to stream or not stream when playing.  The default is for
// audio files to stream.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::SetStreaming(%this, %streaming)
{
   %this.AudioProfiles.RemoveAll();
   
   if (%streaming == false && %this.Streaming == true)
   {
      for (%x = 1; %x <= 8; %x++)
         %this.AudioProfiles.InsertBack(VMPlayerAudioProfile@%x);
   }
   else if (%streaming == true && %this.Streaming == false)
   {
      for (%x = 1; %x <= 8; %x++)
         %this.AudioProfiles.InsertBack(VMPlayerStreamingAudioProfile@%x);
   }

   %this.PrimaryMusicChannel.AudioProfile = %this.AudioProfiles.GetAt(%this.PrimaryMusicChannel.AudioChannel - 1);
   %this.SecondaryMusicChannel.AudioProfile = %this.AudioProfiles.GetAt(%this.SecondaryAudioChannel.AudioChannel - 1);
   %this.Streaming = %streaming;
}

///////////////////////////////////////////////////////////////////////////////
// OpenPlaylistsFromDisk
//
// Reads a previously saved playlists, tracks, track info, and events from the
// specified file.  If successful true is returned, otherwise false.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::OpenPlaylistsFromDisk(%this, %filename)
{
   if (!isfile(%filename))
   {
      VMPlayerPrintDebugMessage(%this, "Unable to open playlist, file '"@%filename@"' not found");
      return false;
   }

   %this.RemoveAllPlaylists();

   $VMPlayerObjTmp = %this;

   return exec(%filename);
}


///////////////////////////////////////////////////////////////////////////////
// SavePlaylistsToDisk
//
// Saves all playlists, tracks, track info, and events in VMPlayer to the
// specified file.  If successful true is returned, otherwise false.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::SavePlaylistsToDisk(%this, %filename)
{
   %file = new FileObject();

   if (!%file.openForWrite(%filename))
   {
      VMPlayerPrintDebugMessage(%this, "Unable to save playlist, filename not valid or filename is read-only");
      return false;
   }

   // Cylce through and remove all tracks and playlists
   for (%x = 0; %x < %this.Playlists.GetCount(); %x++) 
   {
      %playlist = %this.Playlists.GetAt(%x);

      %file.writeLine("%tracks = new ScriptObject() { class=VMPArray; };");

      for (%y = 0; %y < %playlist.Tracks.GetCount(); %y++) 
      {
         %track = %playlist.Tracks.GetAt(%y);

         // Create new events array object
         %file.writeLine("%events = new ScriptObject() { class=VMPArray; };");

         for (%z = 0; %z < %track.Events.GetCount(); %z++) 
         {
            %event = %track.Events.GetAt(%z);

            %file.writeLine("%event = new ScriptObject()");
            %file.writeLine("{");
            %file.writeLine("   class=VMPlayerEvent;");
            %file.writeLine("   Position="@%event.Position@";");
            %file.writeLine("   EventFunction="@%event.EventFunction@";");
            %file.writeLine("};");
            %file.writeLine("%events.InsertBack(%event);");
         }

         %file.writeLine("%track = new ScriptObject()");
         %file.writeLine("{");
         %file.writeLine("   class = VMPlayerTrack;");
         %file.writeLine("   Filename = \""@%track.Filename@"\";");
         %file.writeLine("   Title = \""@%track.Title@"\";");
         %file.writeLine("   Artist = \""@%track.Artist@"\";");
         %file.writeLine("   StartPosition = "@%track.StartPosition@";");
         %file.writeLine("   EndPosition = "@%track.EndPosition@";");
         %file.writeLine("   Events = %events;");
         %file.writeLine("};");
         %file.writeLine("%tracks.InsertBack(%track);");
      }

      %file.writeLine("%playlist = new ScriptObject()");
      %file.writeLine("{");
      %file.writeLine("   class = VMPlayerPlaylist;");
      %file.writeLine("   Name = "@%playlist.Name@";");
      %file.writeLine("   Tracks = %tracks;");
      %file.writeLine("};");
      %file.writeLine("$VMPlayerObjTmp.Playlists.InsertBack(%playlist);");
   }

   %file.close();
   %file.delete();

   VMPlayerPrintDebugMessage(%this, "Playlist "@%filename@" saved");
   
   return true;
}


///////////////////////////////////////////////////////////////////////////////
// AddPlaylist
//
// Adds a new playlist to VMPlayer, with the options of adding 16 songs to that
// playlist.  If successful the index of the new playlist is returned,
// otherwise 0.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::AddPlaylist(%this, %playlistname, %song1, %song2, %song3, %song4, %song5, %song6, %song7, %song8, %song9, %song10, %song11, %song12, %song13, %song14, %song15, %song16)
{
   if (%playlistname $= "")
   {
      VMPlayerPrintDebugMessage(%this, "Unable to add playlist, a playlist name must be specified");
      return 0;
   }

   // Abort if playlist already exists
   if (isObject(VMPlayerGetPlaylist(%this, %playlistname)))
   {
      VMPlayerPrintDebugMessage(%this, "Unable to add playlist '"@%playlistname@"', playlist already exists");
      return 0;
   }

   %tracks = new ScriptObject()
   {
      class = VMPArray;
   };

   %playlist = new ScriptObject()
   {
      class = VMPlayerPlaylist;
      Name = %playlistname;
      Tracks = %tracks;
   };

   %this.Playlists.InsertBack(%playlist);

   VMPlayerPrintDebugMessage(%this, "Playlist '"@%playlistname@"' added");

   if (strlen(%song1) > 0)
      %this.AddSongToPlaylist(%playlistname, %song1);
   if (strlen(%song2) > 0)
      %this.AddSongToPlaylist(%playlistname, %song2);
   if (strlen(%song3) > 0)
      %this.AddSongToPlaylist(%playlistname, %song3);
   if (strlen(%song4) > 0)
      %this.AddSongToPlaylist(%playlistname, %song4);
   if (strlen(%song5) > 0)
      %this.AddSongToPlaylist(%playlistname, %song5);
   if (strlen(%song6) > 0)
      %this.AddSongToPlaylist(%playlistname, %song6);
   if (strlen(%song7) > 0)
      %this.AddSongToPlaylist(%playlistname, %song7);
   if (strlen(%song8) > 0)
      %this.AddSongToPlaylist(%playlistname, %song8);
   if (strlen(%song9) > 0)
      %this.AddSongToPlaylist(%playlistname, %song9);
   if (strlen(%song10) > 0)
      %this.AddSongToPlaylist(%playlistname, %song10);
   if (strlen(%song11) > 0)
      %this.AddSongToPlaylist(%playlistname, %song11);
   if (strlen(%song12) > 0)
      %this.AddSongToPlaylist(%playlistname, %song12);
   if (strlen(%song13) > 0)
      %this.AddSongToPlaylist(%playlistname, %song13);
   if (strlen(%song14) > 0)
      %this.AddSongToPlaylist(%playlistname, %song14);
   if (strlen(%song15) > 0)
      %this.AddSongToPlaylist(%playlistname, %song15);
   if (strlen(%song16) > 0)
      %this.AddSongToPlaylist(%playlistname, %song16);

   return %this.Playlists.GetCount();
}


///////////////////////////////////////////////////////////////////////////////
// RemovePlaylist
//
// Removes the specified playlists from VMPlayer.  This will also remove all
// tracks and events associated with that playlist.  If a matching playlist is
// found and removed true is returned, otherwise false.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::RemovePlaylist(%this, %playlistname)
{
   %playlist = VMPlayerGetPlaylist(%this, %playlistname);

   if (!isObject(%playlist))
   {
      VMPlayerPrintDebugMessage(%this, "Unable to remove playlist '"@%playlistname@"', playlist not found");
      return false;
   }

   // Unload our playlist if this is the one the user wishes
   // to remove.
   if (%this.CurrentPlaylist == %playlist)
      %this.UnloadPlaylist();

   %this.Playlists.RemoveAt(%this.Playlists.Find(%playlist));

   VMPlayerPrintDebugMessage(%this, "Playlist '"@%playlistname@"' removed");

   return true;
}


///////////////////////////////////////////////////////////////////////////////
// RemoveAllPlaylists
//
// Removes all playlists from VMPlayer.  This will, as a result, also remove
// all tracks and events.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::RemoveAllPlaylists(%this)
{
   if (%this.IsPlaylistLoaded())
      %this.UnloadPlaylist();

   if (!isObject(%this.Playlists))
      return;

   // Cylce through and remove all tracks and playlists
   for (%x = 0; %x < %this.Playlists.GetCount(); %x++) 
   {
      %playlist = %this.Playlists.GetAt(%x);

      for (%y = 0; %y < %playlist.Tracks.GetCount(); %y++) 
      {
         %track = %playlist.Tracks.GetAt(%y);

         for (%z = 0; %z < %track.Events.GetCount(); %z++) 
         {
            %track.Events.GetAt(%z).delete();
         }

         %track.Events.delete();
         %track.delete();
      }

      %playlist.Tracks.delete();
      %playlist.delete();
   }

   VMPlayerPrintDebugMessage(%this, "All playlists removed");

   %this.Playlists.RemoveAll();
}


///////////////////////////////////////////////////////////////////////////////
// LoadPlaylist
//
// Loads the specified playlist so it can now be played.  If the specified
// playlist does not exist then false is returned, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::LoadPlaylist(%this, %playlistname)
{
   %playlist = VMPlayerGetPlaylist(%this, %playlistname);
   
   if (!isObject(%playlist))
   {
      VMPlayerPrintDebugMessage(%this, "Unable to load playlist '"@%playlistname@"', playlist not found");
      return false;
   }

   if (isObject(%this.CurrentPlaylist))
   {
      // Don't need to do anything if we already have
      // that playlist loaded.
      if (%this.CurrentPlaylist.Name $= %playlist.Name)
      {
         VMPlayerPrintDebugMessage(%this, "Playlist '"@%playlistname@"' already loaded");
         return true;
      }
   }

   if (%this.IsPlaying() || %this.IsPaused())
      %this.Stop();

   %this.CurrentPlaylist = %playlist;

   if (%playlist.Tracks.GetCount() > 0)
      %this.CurrentTrackIndex = 0;
   else
      %this.CurrentTrackIndex = -1;

   VMPlayerPrintDebugMessage(%this, "Playlist '"@%playlistname@"' loaded");

   return true;
}


///////////////////////////////////////////////////////////////////////////////
// UnloadPlaylist
//
// Unloads the currently loaded playlist, if any.  This will stop any songs
// currently playing in that playlist.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::UnloadPlaylist(%this)
{
   if (!%this.IsPlaylistLoaded())
   {
      VMPlayerPrintDebugMessage(%this, "Unable to unload current playlist, not playlist previously loaded");
      return;
   }
   
   if (%this.IsPlaying() || %this.IsPaused())
      %this.Stop();

   %this.CurrentPlaylist = 0;
   %this.CurrentTrackIndex = -1;
   
   VMPlayerPrintDebugMessage(%this, "Current playlist unloaded");
}


///////////////////////////////////////////////////////////////////////////////
// IsPlaylistLoaded
//
// Returns true if a playlist is currently loaded, otherwise false.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::IsPlaylistLoaded(%this)
{
   return (isObject(%this.CurrentPlaylist));
}


///////////////////////////////////////////////////////////////////////////////
// GetCurrentPlaylistName
//
// Returns the name of the currently loaded playlist.  If no playlist is loaded
// then it returns an empty string.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::GetCurrentPlaylistName(%this)
{
   if (!isObject(%this.CurrentPlaylist))
      return "";

   return %this.CurrentPlaylist.Name;
}


///////////////////////////////////////////////////////////////////////////////
// GetPlaylistCount
//
// Returns the number of added playlists.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::GetPlaylistCount(%this)
{
   return %this.Playlists.GetCount();
}


///////////////////////////////////////////////////////////////////////////////
// GetPlaylistName
//
// Returns the name of the specified playlist index.  If the specified playlist
// index is invalid an empty string is returned.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::GetPlaylistName(%this, %index)
{
   if (%index < 1 || %index > %this.Playlists.GetCount())
      return "";

   return %this.Playlists.GetAt(%index - 1).Name;
}


///////////////////////////////////////////////////////////////////////////////
// AddSongToPlaylist
//
// Adds a new track to the specified playlist.  Optional parameters allow you
// to specify a title and artist, as well as to specify starting and ending
// positions (in seconds) for the track to play.  If the specified playlist
// does not exist 0 is returned, otherwise the new track number for the song.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::AddSongToPlaylist(%this, %playlistname, %song, %title, %artist, %start_pos, %end_pos)
{
   %playlist = VMPlayerGetPlaylist(%this, %playlistname);

   if (!isObject(%playlist))
   {
      VMPlayerPrintDebugMessage(%this, "Unable to add song '"@%song@"' to playlist '"@%playlistname@"', playlist does not exist");
      return 0;
   }
   
   if (%song $= "")
   {
      VMPlayerPrintDebugMessage(%this, "Unable to add song '"@%song@"' to playlist '"@%playlistname@"', song name invalid");
      return 0;
   }

   // Make sure file exists
   if (!isfile(%song))
   {
      VMPlayerPrintDebugMessage(%this, "Unable to add song '"@%song@"' to playlist '"@%playlistname@"', song file does not exist");
      return 0;
   }

   // Make sure song file is valid, and if so grab song duration
   %filename = %this.CurrentMusicChannel.AudioProfile.filename;
   %this.CurrentMusicChannel.AudioProfile.filename = %song;
   %handle = alxPlay(%this.CurrentMusicChannel.AudioProfile);

   if (%handle == 0)
   {
      VMPlayerPrintDebugMessage(%this, "Unable to add song '"@%song@"' to playlist '"@%playlistname@"', song file does not appear to be a valid audio file");
      %this.CurrentMusicChannel.AudioProfile.filename = %filename;
      return 0;
   }

   %song_duration = alxGetStreamDuration(%handle);
   alxStop(%handle);

   if (%end_pos $= "" || %end_pos < %start_pos)
      %end_pos = %song_duration;

   // Add a new song to our playlist
   %track = VMPlayerGenerateTrackInfo(%song, %title, %artist, %start_pos, %end_pos);

   %playlist.Tracks.InsertBack(%track);

   // If this is the first track added, set our current
   if (%playlist.Tracks.GetCount() == 1)
      %this.SetCurrentTrackNumber(1);
   
   VMPlayerPrintDebugMessage(%this, "Song '"@%song@"' added to playlist '"@%playlistname@"'");
   
   return %playlist.Tracks.GetCount();
}


///////////////////////////////////////////////////////////////////////////////
// RemoveTrack
//
// Removes the specified track from the specified playlist.  This will also
// remove events associated with that track.  If a matching playlist and track
// is found and removed true is returned, otherwise false.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::RemoveTrack(%this, %playlistname, %tracknumber)
{
   %playlist = VMPlayerGetPlaylist(%this, %playlistname);

   if (!isObject(%playlist))
   {
      VMPlayerPrintDebugMessage(%this, "Unable to remove track '"@%tracknumber@"' from playlist '"@%playlistname@"', playlist does not exist");
      return false;
   }
      
   if (%tracknumber <= 0 || %tracknumber > %playlist.Tracks.GetCount())
   {
      VMPlayerPrintDebugMessage(%this, "Unable to remove track '"@%tracknumber@"' from playlist '"@%playlistname@"', track number does not exist");
      return false;
   }

   // If we are removing the currently playing song, stop the music and
   // move our current track, if necessary.
   if (%this.CurrentPlaylist == %playlist && %tracknumber == %this.GetCurrentTrackNumber())
   {
      if (%this.IsPlaying() || %this.IsPaused())
         %this.Stop();

      if (%tracknumber == %playlist.Tracks.GetCount())
         %this.CurrentTrackIndex--;
   }

   %playlist.Tracks.RemoveAt(%tracknumber - 1);

   VMPlayerPrintDebugMessage(%this, "Track '"@%tracknumber@"' removed from playlist '"@%playlistname@"'");

   return true;
}


///////////////////////////////////////////////////////////////////////////////
// RemoveAllTracks
//
// Removes all tracks from the specified playlist.  This will also remove all 
// events associated with these tracks.  If a matching playlist is not found
// false is returned, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::RemoveAllTracks(%this, %playlistname)
{
   %playlist = VMPlayerGetPlaylist(%this, %playlistname);

   if (!isObject(%playlist))
   {
      VMPlayerPrintDebugMessage(%this, "Unable to remove all tracks from playlist '"@%playlistname@"', playlist does not exist");
      return false;
   }

   // If we are removing the currently playing song, stop the music and
   // move our current track.
   if (%this.CurrentPlaylist == %playlist)
   {
      if (%this.IsPlaying() || %this.IsPaused())
         %this.Stop();

      %this.CurrentTrackIndex = -1;
   }

   %playlist.Tracks.RemoveAll();

  VMPlayerPrintDebugMessage(%this, "All tracks removed from playlist '"@%tracknumber@"'");

   return true;
}


///////////////////////////////////////////////////////////////////////////////
// GetTrackCount
//
// Returns the number of tracks in the specified playlist.  If no matching
// playlist is found -1 is returned.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::GetTrackCount(%this, %playlistname)
{
   %playlist = VMPlayerGetPlaylist(%this, %playlistname);

   if (!isObject(%playlist))
      return -1;

   return %playlist.Tracks.GetCount();
}


///////////////////////////////////////////////////////////////////////////////
// GetCurrentTrackNumber
//
// Returns the track number of the currently selected track.  If no playlist
// is loaded then 0 is returned.  A song does not have to be playing for a
// track to be selected.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::GetCurrentTrackNumber(%this)
{
   if (!%this.IsPlaylistLoaded() || %this.CurrentPlaylist.Tracks.GetCount() == 0)
      return 0;

   return (%this.CurrentTrackIndex + 1);
}


///////////////////////////////////////////////////////////////////////////////
// SetCurrentTrackNumber
//
// Selects the specified track number.  If a song is currently playing the song
// is stopped.  This function does not automatically play the selected track.
// If the specified track number is not found false is returned, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::SetCurrentTrackNumber(%this, %tracknumber)
{
   if (!%this.IsPlaylistLoaded() || %tracknumber < 1 || %tracknumber > %this.CurrentPlaylist.Tracks.GetCount())
      return false;

   if (%this.IsPlaying() || %this.IsPaused())
      %this.Stop();

   %this.CurrentTrackIndex = %tracknumber - 1;

   return true;
}


///////////////////////////////////////////////////////////////////////////////
// GetTrackFilename
//
// Returns the filename of the specified track number in the specified playlist.
// If no matching playlist is found or the track number is invalid an empty
// string is returned.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::GetTrackFilename(%this, %playlistname, %tracknumber)
{
   %track = VMPlayerGetTrack(%this, %playlistname, %tracknumber);

   if (isObject(%track))
      return %track.Filename;

   return "";
}


///////////////////////////////////////////////////////////////////////////////
// GetTrackTitle
//
// Returns the song title of the specified track number in the specified
// playlist.  If no matching playlist is found or the track number is invalid
// an empty string is returned.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::GetTrackTitle(%this, %playlistname, %tracknumber)
{
   %track = VMPlayerGetTrack(%this, %playlistname, %tracknumber);

   if (isObject(%track))
      return %track.Title;

   return "";
}


///////////////////////////////////////////////////////////////////////////////
// SetTrackTitle
//
// Changes the song title of the specified track number in the specified
// playlist.  If no matching playlist is found or the track number is invalid
// false is returned, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::SetTrackTitle(%this, %playlistname, %tracknumber, %title)
{
   if (%title $= "")
      return false;

   %track = VMPlayerGetTrack(%this, %playlistname, %tracknumber);
   if (!isObject(%track))
      return false;

   %track.Title = %title;

   return true;
}


///////////////////////////////////////////////////////////////////////////////
// GetTrackArtist
//
// Returns the song artist of the specified track number in the specified
// playlist.  If no matching playlist is found or the track number is invalid
// an empty string is returned.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::GetTrackArtist(%this, %playlistname, %tracknumber)
{
   %track = VMPlayerGetTrack(%this, %playlistname, %tracknumber);

   if (isObject(%track))
      return %track.Artist;

   return "";
}


///////////////////////////////////////////////////////////////////////////////
// SetTrackArtist
//
// Changes the song artis of the specified track number in the specified
// playlist.  If no matching playlist is found or the track number is invalid
// false is returned, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::SetTrackArtist(%this, %playlistname, %tracknumber, %artist)
{
   if (%artist $= "")
      %artist = "Unknown";

   %track = VMPlayerGetTrack(%this, %playlistname, %tracknumber);
   if (!isObject(%track))
      return false;

   %track.Artist = %artist;

   return true;
}


///////////////////////////////////////////////////////////////////////////////
// GetTrackStartPosition
//
// Returns the position (in seconds) to start the song for the specified track
// number in the specified playlist.  If no starting position was ever set on
// the specified track, then 0 is returned, which means the song will start at
// the very beginning of the song.  If no matching playlist is found, or the
// track number is invalid -1 is returned.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::GetTrackStartPosition(%this, %playlistname, %tracknumber)
{
   %track = VMPlayerGetTrack(%this, %playlistname, %tracknumber);

   if (isObject(%track))
      return %track.StartPosition;

   return -1;
}


///////////////////////////////////////////////////////////////////////////////
// SetTrackStartPosition
//
// Sets the position (in seconds) to start the song for the specified track
// number in the specified playlist.  Note that the starting position cannot
// be higher than the ending position in the song.  If no matching playlist is
// found or the track number is invalid false is returned, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::SetTrackStartPosition(%this, %playlistname, %tracknumber, %start_pos)
{
   %track = VMPlayerGetTrack(%this, %playlistname, %tracknumber);
   if (!isObject(%track))
      return false;

   if (%start_pos > %track.EndPosition)
      return false;

   %track.StartPosition = %start_pos;

   return true;
}


///////////////////////////////////////////////////////////////////////////////
// GetTrackEndPosition
//
// Returns the position (in seconds) to end the song for the specified track
// number in the specified playlist.  If no ending position was ever set on the
// specified track, then position at the very end of the song is returned.  If
// no matching playlist is found, or the track number is invalid -1 is returned.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::GetTrackEndPosition(%this, %playlistname, %tracknumber)
{
   %track = VMPlayerGetTrack(%this, %playlistname, %tracknumber);

   if (isObject(%track))
      return %track.EndPosition;

   return -1;
}


///////////////////////////////////////////////////////////////////////////////
// SetTrackEndPosition
//
// Sets the position (in seconds) to end the song for the specified track
// number in the specified playlist.  Note that the ending position cannot be
// lower than the startng position in the song.  If no matching playlist is
// found or the track number is invalid false is returned, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::SetTrackEndPosition(%this, %playlistname, %tracknumber, %end_pos)
{
   %track = VMPlayerGetTrack(%this, %playlistname, %tracknumber);
   if (!isObject(%track))
      return false;

   if (%end_pos < %track.StartPosition)
      return false;

   %track.EndPosition = %end_pos;

   return true;
}


///////////////////////////////////////////////////////////////////////////////
// GetTrackDuration
//
// Returns the duration (in seconds) of the song for the specified track number
// in the specified playlist.  If no matching playlist is found, or the track
// number is invalid -1 is returned.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::GetTrackDuration(%this, %playlistname, %tracknumber)
{  
   %track = VMPlayerGetTrack(%this, %playlistname, %tracknumber);
   
   if (isObject(%track))
      return (%track.EndPosition - %track.StartPosition);
      
   return -1;
}


///////////////////////////////////////////////////////////////////////////////
// GetSongFilename
//
// Returns the filename of the currently playing song.  If no song is playing
// an empty string is returned.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::GetSongFilename(%this)
{
   if (isObject(%this.CurrentSong))
      return %this.CurrentSong.Filename;

   return "";
}


///////////////////////////////////////////////////////////////////////////////
// GetSongTitle
//
// Returns the song title of the currently playing song.  If no song is playing
// an empty string is returned.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::GetSongTitle(%this)
{
   if (isObject(%this.CurrentSong))
      return %this.CurrentSong.Title;

   return "";
}


///////////////////////////////////////////////////////////////////////////////
// SetSongTitle
//
// Changes the song title of the currently playing song.  If no song is playing
// false is returned, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::SetSongTitle(%this, %title)
{
   if (%title $= "" || !isObject(%this.CurrentSong))
      return false;

   %this.CurrentSong.Title = %title;

   return true;
}


///////////////////////////////////////////////////////////////////////////////
// GetSongArtist
//
// Returns the song artist of the currently playing song.  If no song is
// playing an empty string is returned.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::GetSongArtist(%this)
{
   if (isObject(%this.CurrentSong))
      return %this.CurrentSong.Artist;

   return "";
}


///////////////////////////////////////////////////////////////////////////////
// SetSongArtist
//
// Changes the song artist of the currently playing song.  If no song is
// playing false is returned, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::SetSongArtist(%this, %artist)
{
   if (!isObject(%this.CurrentSong))
      return false;

   if (%artist $= "")
      %artist = "Unknown";

   %this.CurrentSong.Artist = %artist;

   return true;
}


///////////////////////////////////////////////////////////////////////////////
// GetSongStartPosition
//
// Returns the position (in seconds) to start the song for the currently
// playing song.  If no starting position was ever set on the song, then 0 is
// returned, which means the song will start at the very beginning of the song.
// If no song is playing -1 is returned.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::GetSongStartPosition(%this)
{
   if (isObject(%this.CurrentSong))
      return %this.CurrentSong.StartPosition;

   return -1;
}


///////////////////////////////////////////////////////////////////////////////
// SetSongStartPosition
//
// Sets the position (in seconds) to start the song for the currently playing
// song.  Note that the starting position cannot be higher than the ending
// position in the song.  If song is playing false is returned, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::SetSongStartPosition(%this, %start_pos)
{
   if (!isObject(%this.CurrentSong))
      return false;

   if (%start_pos > %this.CurrentSong.EndPosition)
      return false;

   %this.CurrentSong.StartPosition = %start_pos;

   return true;
}


///////////////////////////////////////////////////////////////////////////////
// GetSongEndPosition
//
// Returns the position (in seconds) to end the song for the currently playing
// song.  If no ending position was ever set on the song, then position at the
// very end of the song is returned.  If song is playing -1 is returned.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::GetSongEndPosition(%this)
{
   if (isObject(%this.CurrentSong))
      return %this.CurrentSong.EndPosition;

   return -1;
}


///////////////////////////////////////////////////////////////////////////////
// SetSongEndPosition
//
// Sets the position (in seconds) to end the song for the currently playing
// song.  Note that the ending position cannot be lower than the startng
// position in the song.  If no song is playing false is returned, otherwise
// true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::SetSongEndPosition(%this, %end_pos)
{
   if (!isObject(%this.CurrentSong))
      return false;

   if (%end_pos < %this.CurrentSong.StartPosition)
      return false;

   %this.CurrentSong.EndPosition = %end_pos;

   return true;
}


///////////////////////////////////////////////////////////////////////////////
// GetSongDuration
//
// Returns the duration (in seconds) of the song for the currently playing
// song.  If no song is playing -1 is returned.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::GetSongDuration(%this)
{
   if (isObject(%this.CurrentSong))
      return (%this.CurrentSong.EndPosition - %this.CurrentSong.StartPosition);
      
   return -1;
}


///////////////////////////////////////////////////////////////////////////////
// AddEventToTrack
//
// Adds an event function to the specified track in the specified playlist.
// This event is fired whenever the specified position in the track is played.
// Note that only one event function can be added to the SAME position in the
// track.  If no matching playlist is found or the track number is invalid
// false is returned, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::AddEventToTrack(%this, %playlistname, %tracknumber, %eventfunc, %pos)
{
   %track = VMPlayerGetTrack(%this, %playlistname, %tracknumber);

   if (!isObject(%track))
   {
      VMPlayerPrintDebugMessage(%this, "Unable to add event to track '"@%tracknumber@"' of playlist '"@%playlistname@"', track or playlist does not exist");
      return false;
   }
   
   // Replace any previous events for this position
   %this.RemoveEventFromTrack(%playlistname, %tracknumber, %pos);

   %event = new ScriptObject()
   {
      class = VMPlayerEvent;
      Position = %pos;
      EventFunction = %eventfunc;
   };

   %track.Events.InsertBack(%event);

   VMPlayerPrintDebugMessage(%this, "Event added to track '"@%tracknumber@"' of playlist '"@%playlistname@"'");

   return true;
}


///////////////////////////////////////////////////////////////////////////////
// RemoveEventFromTrack
//
// Removes an event from the specified position in the specified track in the
// specified playlist.  If no matching playlist is found, the track number is
// invalid, or the position does not have an event added false is returned,
// otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::RemoveEventFromTrack(%this, %playlistname, %tracknumber, %pos)
{
   %track = VMPlayerGetTrack(%this, %playlistname, %tracknumber);

   if (!isObject(%track))
      return false;

   for (%x = 0; %x < %track.Events.GetCount(); %x++)
   {

      // To make sure we only fire the event once we check against
      // both the current pos and last pos.
      if (%track.Events.GetAt(%x).Position == %pos)
      {
         %track.Events.RemoveAt(%x);
         return true;
      }
   }

   return false;
}


///////////////////////////////////////////////////////////////////////////////
// RemoveAllEventsFromTrack
//
// Removes all events from the specified position in the specified track in the
// specified playlist.  If no matching playlist is found or the track number is
// invalid false is returned, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::RemoveAllEventsFromTrack(%this, %playlistname, %tracknumber)
{
   %track = VMPlayerGetTrack(%this, %playlistname, %tracknumber);

   if (!isObject(%track))
      return false;

   %track.Events.RemoveAll();

   return true;
}


///////////////////////////////////////////////////////////////////////////////
// AddEventToSong
//
// Adds an event function to the currently playing song. This event is fired
// whenever the specified position in the track is played.  Note that only one
// event function can be added to the SAME position in the same song.  If no
// song is playing false is returned, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::AddEventToSong(%this, %eventfunc, %pos)
{
   if (!isObject(%this.CurrentSong))
      return false;

   // Replace any previous events for this position
   %this.RemoveEventFromSong(%pos);

   %event = new ScriptObject()
   {
      class = VMPlayerEvent;
      Position = %pos;
      EventFunction = %eventfunc;
   };

   %this.CurrentSong.Events.InsertBack(%event);

   return true;
}


///////////////////////////////////////////////////////////////////////////////
// RemoveEventFromSong
//
// Removes an event from the currently playing song.  If no song is playing or
// the position does not have an event added false is returned, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::RemoveEventFromSong(%this, %pos)
{
   if (!isObject(%this.CurrentSong))
      return false;

   for (%x = 0; %x < %this.CurrentSong.Events.GetCount(); %x++)
   {

      // To make sure we only fire the event once we check against
      // both the current pos and last pos.
      if (%this.CurrentSong.Events.GetAt(%x).Position == %pos)
      {
         %this.CurrentSong.Events.RemoveAt(%x);
         return true;
      }
   }

   return false;
}


///////////////////////////////////////////////////////////////////////////////
// RemoveAllEventsFromSong
//
// Removes all events from the currently playing song.  If no song is playing
// false is returned, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::RemoveAllEventsFromSong(%this)
{
   if (!isObject(%this.CurrentSong))
      return false;

   %this.CurrentSong.Events.RemoveAll();

   return true;
}


///////////////////////////////////////////////////////////////////////////////
// SetVolume
//
// Sets the music volume.  Valid volume values range from 0.0 to 1.0.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::SetVolume(%this, %volume)
{
   if (%volume > 1.0)
      %volume = 1.0;
   if (%volume < 0.0)
      %volume = 0.0;

   %this.CurrentVolume = %volume;

   // If we aren't fading in/out we can actually change the volume.
   if (%this.FadeOutHandle == 0 && %this.CrossFadeOutHandle == 0 )
      alxSetChannelVolume(VMPlayerGetOtherMusicChannel(%this, %this.CurrentMusicChannel).AudioChannel, %volume);

   if (%this.FadeInHandle == 0 && %this.CrossFadeInHandle == 0)
      alxSetChannelVolume(%this.CurrentMusicChannel.AudioChannel, %volume);
}


///////////////////////////////////////////////////////////////////////////////
// GetVolume
//
// Returns the intended music volume.  Note that this NOT return a lower volume
// level when a song is fading, but rather the normal music volume when playing
// songs.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::GetVolume(%this)
{
   return %this.CurrentVolume;
}


///////////////////////////////////////////////////////////////////////////////
// GetNextTrackDelay
//
// Returns the specified number of seconds to delay between track changes.
// This setting is ignored when fade or cross fade is enabled.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::GetNextTrackDelay(%this)
{
   return (%this.NextTrackDelayMS / 1000);
}


///////////////////////////////////////////////////////////////////////////////
// SetNextTrackDelay
//
// Sets a specified number of seconds to delay track changes.  This setting is
// ignored when fade or cross fade is enabled.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::SetNextTrackDelay(%this, %seconds)
{
   %this.NextTrackDelayMS = %seconds * 1000;
}


///////////////////////////////////////////////////////////////////////////////
// EnableFade
//
// Enables or disables fading of music.  Enabling fading will cause all songs
// to start playing with the volume set to 0.0 and gradually increase until the
// normal music volume is reached, and will also cause all songs that stop to
// gradually decrease their volume level to 0.0 before actually stopping the
// song.  When enabled, fading IS used between track changes.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::EnableFade(%this, %use_fade)
{
   %this.Fade = %use_fade;
}


///////////////////////////////////////////////////////////////////////////////
// IsFadeEnabled
//
// Returns true if fading is enabled, otherwise false.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::IsFadeEnabled(%this)
{
   return %this.Fade;
}


///////////////////////////////////////////////////////////////////////////////
// EnableCrossFade
//
// Enables or disables cross fading of music.  Enabling cross fading will cause
// all songs which are interupted to play another song to start fading out the
// old song while simultaneously fading in the new song.  Note that if a song
// is manually stopped before attempting to play a new song cross fading does
// not occur.  When enabled, cross fading IS used to transition between track
// changes.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::EnableCrossFade(%this, %use_crossfade)
{
   %this.CrossFade = %use_crossfade;
}


///////////////////////////////////////////////////////////////////////////////
// IsCrossFadeEnabled
//
// Returns true if cross fading is enabled, otherwise false.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::IsCrossFadeEnabled(%this)
{
   return %this.CrossFade;
}


///////////////////////////////////////////////////////////////////////////////
// SetFadeDuration
//
// Sets how long a song fade or cross fade lasts, in seconds.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::SetFadeDuration(%this, %use_fade)
{
   %this.FadeDuration = %use_fade;
}


///////////////////////////////////////////////////////////////////////////////
// GetFadeDuration
//
// Returns the number of seconds a song fade or cross fade lasts.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::GetFadeDuration(%this)
{
   return %this.FadeDuration;
}


///////////////////////////////////////////////////////////////////////////////
// EnableRandom
//
// Enables or disables the random selection of tracks in playlists.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::EnableRandom(%this, %use_random)
{
   %this.Random = %use_random;
}


///////////////////////////////////////////////////////////////////////////////
// IsRandomEnabled
//
// Returns true if random is enabled, otherwise false.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::IsRandomEnabled(%this)
{
   return %this.Random;
}


///////////////////////////////////////////////////////////////////////////////
// EnableRepeat
//
// Enables or disables the repeating of songs or playlists.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::EnableRepeat(%this, %use_repeat)
{
   %this.Repeat = %use_repeat;
}


///////////////////////////////////////////////////////////////////////////////
// IsRepeatEnabled
//
// Returns true if random is enabled, otherwise false.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::IsRepeatEnabled(%this)
{
   return %this.Repeat;
}


///////////////////////////////////////////////////////////////////////////////
// EnableDebug
//
// Enables or disables debug mode.  Enabling debug mode will cause debug
// information to be displayed constantly in the console.  This is useful when
// VMPlayer doesn't seem to be working as expected.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::EnableDebug(%this, %enable_debug)
{
   %this.Debug = %enable_debug;
}


///////////////////////////////////////////////////////////////////////////////
// IsDebugEnabled
//
// Returns true if cross fading is enabled, otherwise false.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::IsDebugEnabled(%this)
{
   return %this.Debug;
}


function VMPlayer::DebugDisplayAll(%this)
{
   echo("");

   if (%this.Playlists.GetCount() == 0)
   {
      echo("VMPlayer: No playlists added");
      return;
   }

   for (%x = 0; %x < %this.Playlists.GetCount(); %x++)
   {
      echo(%this.Playlists.GetAt(%x).Name);
      echo("------------------");
         %this.DebugPrintTracks(%this.Playlists.GetAt(%x).Name);
      echo("");
   }
}


function VMPlayer::DebugDisplayPlaylists(%this)
{
   if (%this.Playlists.GetCount() == 0)
   {
      echo("No playlists added");
      return;
   }

   for (%x = 0; %x < %this.Playlists.GetCount(); %x++)
   {
      echo(%this.Playlists.GetAt(%x).Name);
   }
}


function VMPlayer::DebugDisplayTracks(%this, %playlistname)
{
   if (%this.Playlists.GetCount() == 0)
   {
      echo("VMPlayer: No playlists added");
      return;
   }

   %playlist = VMPlayerGetPlaylist(%this, %playlistname);

   if (!isObject(%playlist))
   {
      echo("VMPlayer: Playlist not found");
      return;
   }

   if (%playlist.Tracks.GetCount() == 0)
   {
      echo("VMPlayer: No tracks added");
      return;
   }

   for (%x = 0; %x < %playlist.Tracks.GetCount(); %x++)
   {
      echo(%playlist.Tracks.GetAt(%x).Title);
   }
}


///////////////////////////////////////////////////////////////////////////////
// PlaySong
//
// Loads and plays the specified song.  If a playlist was previously loaded it
// will be unloaded.  If the song was successfully played true is returned,
// otherwise false.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::PlaySong(%this, %song, %title, %artist, %start_pos, %end_pos)
{
   if (!isfile(%song))
   {
      VMPlayerPrintDebugMessage(%this, "Unable to play song '"@%song@"', song file does not exist");
      return false;
   }
 
   // We're no longer associated with a playlist
   if (%this.IsPlaylistLoaded())
      %this.UnloadPlaylist();

   // We don't start the song over if they tell us to play the
   // currently playing song 
   if (%this.IsPlaying() || %this.IsPaused())
   {
      // Note that AudioProfile.filename will change the
      // actual path if need be (ie, replaces ~ with the name
      // of your actual mod), so we need to make sure both
      // old and new song are in the same format before
      // comparing.
      %CurrentFilename = %this.CurrentMusicChannel.AudioProfile.filename;
      %this.CurrentMusicChannel.AudioProfile.filename = %song;

      if (%CurrentFilename $= %this.CurrentMusicChannel.AudioProfile.filename)
      {
         if (%this.IsPaused())
            %this.Unpause();

         VMPlayerPrintDebugMessage(%this, "Song '"@%song@"' already playing");
         return true;
      }
   }

   if (%this.IsCrossFadeEnabled() && (%this.IsPlaying() || %this.IsPaused()))
      VMPlayerCrossFadeOut(%this);
   else if (%this.IsPlaying() || %this.IsPaused())
      %this.Stop();

   %song = VMPlayerGenerateTrackInfo(%song, %title, %artist, %start_pos, %end_pos);

   VMPlayerPlay(%this, %song);

   if (!%this.IsPlaying())
   {
      %song.delete();
      return false;
   }

   %song_duration = alxGetStreamDuration(%this.CurrentMusicChannel.AudioHandle);
   if (%song.EndPosition == -1)
      %end_pos = %song_duration;

   return true;
}


///////////////////////////////////////////////////////////////////////////////
// PlayTrack
//
// Plays the specified track in a playlist.  If no playlist has been loaded, or
// if the track is invalid then false is returned, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::PlayTrack(%this, %tracknumber)
{
   if (!%this.IsPlaylistLoaded())
   {
      VMPlayerPrintDebugMessage(%this, "Unable to play track '"@%tracknumber@"', no playlist loaded");
      return false;
   }
   
   if (%tracknumber < 0 || %tracknumber > %this.CurrentPlaylist.Tracks.GetCount())
   {
      VMPlayerPrintDebugMessage(%this, "Unable to play track '"@%tracknumber@"', track is invalid");
      return false;
   }

   // We don't start the song over if they tell us to play the
   // currently playing song.
   if (%this.IsPlaying() && %this.CurrentTrackIndex == %tracknumber - 1)
   {
      VMPlayerPrintDebugMessage(%this, "Track '"@%tracknumber@"' already playing");
      return true;
   }

   if (%this.IsPaused() && %this.CurrentTrackIndex == %tracknumber - 1)
   {
      %this.Unpause();
      VMPlayerPrintDebugMessage(%this, "Track '"@%tracknumber@"' already playing");
      return true;
   }  

   if (%this.IsCrossFadeEnabled() && (%this.IsPlaying() || %this.IsPaused()))
      VMPlayerCrossFadeOut(%this);
   else if (%this.IsPlaying() || %this.IsPaused())
      %this.Stop();

   // Create a list of all tracks to play.
   %this.NextTracks.RemoveAll();
   %this.NextTracks.InsertBack(%tracknumber - 1);
   %this.NextTrackIndex = 0;
   %this.CurrentTrackIndex = %this.NextTracks.GetAt(%this.NextTrackIndex);

   %this.CurrentTrackIndex = %tracknumber - 1;

   return VMPlayerPlay(%this, %this.CurrentPlaylist.Tracks.GetAt(%this.CurrentTrackIndex));
}


///////////////////////////////////////////////////////////////////////////////
// Play
//
// Plays all songs in the current playlist starting with the currently selected
// track.  If no playlist has been loaded, or if no tracks have been added to
// the playlist then false is returned, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::Play(%this)
{
   if (!%this.IsPlaylistLoaded())
   {
      VMPlayerPrintDebugMessage(%this, "Unable to play, no playlist loaded");
      return false;
   }
   
   if (%this.CurrentPlaylist.Tracks.GetCount() == 0)
   {
      VMPlayerPrintDebugMessage(%this, "Unable to play, no tracks added");
      return false;
   }

   // We don't start the song over if we are already playing
   if (%this.IsPlaying())
   {
      VMPlayerPrintDebugMessage(%this, "Song already playing");
      return true;
   }

   if (%this.IsPaused())
   {
      %this.Unpause();
      VMPlayerPrintDebugMessage(%this, "Song already playing");
      return true;
   }

   if (%this.IsCrossFadeEnabled() && (%this.IsPlaying() || %this.IsPaused()))
      VMPlayerCrossFadeOut();
   else if (%this.IsPlaying() || %this.IsPaused())
      %this.Stop();

   // Create a list of all tracks to play.
   VMPlayerGenerateNextTrackArray(%this);
   %this.NextTrackIndex = 0;
   %this.CurrentTrackIndex = %this.NextTracks.GetAt(%this.NextTrackIndex);

   return VMPlayerPlay(%this, %this.CurrentPlaylist.Tracks.GetAt(%this.CurrentTrackIndex), true);
}


///////////////////////////////////////////////////////////////////////////////
// IsPlaying
//
// Returns true if VMPlayer is currently playing a song, otherwise false.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::IsPlaying(%this)
{
   // Technically our current channel may have paused itself until the other
   // channel finishes fading.  In this case we don't want to actually report
   // that we are paused, because music IS playing.
   if (%this.FadeOutHandle != 0 && alxIsPaused(%this.CurrentMusicChannel.AudioHandle))
      return true;

   return alxIsPlaying(%this.CurrentMusicChannel.AudioHandle);
}


///////////////////////////////////////////////////////////////////////////////
// IsPaused
//
// Returns true if the VMPlayer was playing a song but is currently paused,
// otherwise false.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::IsPaused(%this)
{
   // Technically our current channle may have paused itself until the other
   // channel finishes fading.  In this case we don't want to actually report
   // that we are paused, because music IS playing.
   if (%this.FadeOutHandle != 0 && !alxIsPaused(VMPlayerGetOtherMusicChannel(%this, %this.CurrentMusicChannel).AudioHandle))
      return false;

   return alxIsPaused(%this.CurrentMusicChannel.AudioHandle);
}


///////////////////////////////////////////////////////////////////////////////
// Pause
//
// Pauses the currently playing song.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::Pause(%this)
{
   if (%this.IsPlaying())
   {
      alxPause(%this.CurrentMusicChannel.AudioHandle);

      if (VMPlayerGetOtherMusicChannel(%this, %this.CurrentMusicChannel).AudioHandle != 0)
         alxPause(VMPlayerGetOtherMusicChannel(%this, %this.CurrentMusicChannel).AudioHandle);
   }
   else if (%this.NextTrackHandle != 0)
      %this.NextTrackPause = true;
}


///////////////////////////////////////////////////////////////////////////////
// Unpause
//
// Unpauses a currently paused song.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::Unpause(%this)
{
   if (%this.IsPaused())
   {
      // Unpause our other channel if we were fading or cross fading
      if (%this.FadeOutHandle != 0 || %this.CrossFadeOutHandle != 0)
         alxUnpause(VMPlayerGetOtherMusicChannel(%this, %this.CurrentMusicChannel).AudioHandle);

      // Don't unpause our current channel if we were fading out
      if (%this.FadeOutHandle == 0)
         alxUnpause(%this.CurrentMusicChannel.AudioHandle);
   }
 
   %this.NextTrackPause = false;
}


///////////////////////////////////////////////////////////////////////////////
// Stop
//
// Stops the currently playing song.  Note that if fading is turned on the song
// will not immediately stop, but instead the volume will fade to nothing and
// then the song will stop.  If you call Stop() and immediately call Play() when
// fading is enabled, then the first song will fade out completely before the
// second song starts to fade in.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::Stop(%this)
{
   if (%this.IsFadeEnabled())
   {
      // Don't restart the fade-out if we are already fading out...
      if (%this.FadeOutHandle == 0)
         VMPlayerFadeOut(%this, %this.CurrentMusicChannel);
      else if (%this.FadeInHandle != 0)
      {
         // Cancel any pending fade-ins
         cancel(%this.FadeInHandle);
         %this.FadeInHandle = 0;
         VMPlayerStopChannel(%this, %this.CurrentMusicChannel);
      }

      return;
   }

   // Cancel any pending fade-ins
   cancel(%this.FadeInHandle);
   %this.FadeInHandle = 0;

   // Cancel any pending cross fade ins/outs
   cancel(%this.CrossFadeInHandle);
   cancel(%this.CrossFadeOutHandle);
   %this.CrossFadeInHandle = 0;
   %this.CrossFadeOutHandle = 0;

   VMPlayerStop(%this);
}


///////////////////////////////////////////////////////////////////////////////
// GetSongPosition
//
// Returns the current position of the currently playing song, in seconds.  If
// no song is currently playing it returns -1.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::GetSongPosition(%this)
{
   if (%this.IsPlaying() || %this.IsPaused())
      // We actualy will return a position relative
      // to the starting position in the song.
      return (alxGetStreamPosition(%this.CurrentMusicChannel.AudioHandle) - %this.CurrentSong.StartPosition);

   return -1;
}


///////////////////////////////////////////////////////////////////////////////
// SetSongPosition
//
// Sets the current position of the currently playing song, in seconds.  If no
// song is currently playing it returns false, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::SetSongPosition(%this, %seconds)
{
   if (%this.IsPlaying() || %this.IsPaused())
   {
      // We are expectin g a time value relative to the actual
      // starting time, so we need to find the absolute value.
      %seconds = %this.CurrentSong.StartPosition + %seconds;

      %ret = alxSeekToStreamPosition(%this.CurrentMusicChannel.AudioHandle, %seconds);
      %this.PreviousPosition = %seconds - 0.001;

      VMPlayerCheckSongPosition(%this);

      if (%ret == true)
      {
         // We need to take into consideration fading.  For instance, if
         // we start a song and are half-way faded in and the user sets
         // the position to near the end of the song, then we need to
         // cancel the fade in and instead fade out.

         %time_left = %this.GetSongDuration() - %this.GetSongPosition();
         if (%time_left <= %this.FadeDuration && %this.IsFadeEnabled())
            VMPlayerFadeOut(%this, %this.CurrentMusicChannel);
      }

      return %ret;
   }

   return false;
}


///////////////////////////////////////////////////////////////////////////////
// JumpBackward
//
// Jumps backward in the currently playing song from its current position by
// the specified number of seconds.  If no song is currently playing it returns
// false, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::JumpBackward(%this, %seconds)
{
   if (%this.IsPlaying() || %this.IsPaused())
   {
      %new_pos = %this.GetSongPosition() - %seconds;

      if (%new_pos < 0)
         %new_pos = 0;

      %this.SetSongPosition(%new_pos);

      return true;
   }

   return false;
}


///////////////////////////////////////////////////////////////////////////////
// JumpForward
//
// Jumps forward in the currently playing song from its current position by
// the specified number of seconds.  If no song is currently playing it returns
// false, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::JumpForward(%this, %seconds)
{
   if (%this.IsPlaying() || %this.IsPaused() )
   {
      %new_pos = %this.GetSongPosition() + %seconds;

      if (%new_pos > %this.GetSongDuration())
         %new_pos =  %this.GetSongDuration();

      %this.SetSongPosition(%new_pos);

      return true;
   }

   return false;
}


///////////////////////////////////////////////////////////////////////////////
// SkipBackward
//
// Skips back to the beginning of a song, or if already at the beginning and
// a playlist is loaded and the currently playing song is not the first track
// then it will skip to the previous track.  If no song is currently playing it
// returns false, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::SkipBackward(%this)
{
   if (%this.IsPlaying() || %this.IsPaused())
   {
      // Create a backup of our track if no playlist is loaded, as
      // it gets automatically deleted by a stop/fade.
      if (!%this.IsPlaylistLoaded())
         %song = VMPlayerCopyTrackInfo(%this, %this.CurrentSong);

      %was_paused = %this.IsPaused();
	  %pos = %this.GetSongPosition();

      if (%this.IsCrossFadeEnabled())
         VMPlayerCrossFadeOut(%this);
      else if (%this.IsFadeEnabled())
         VMPlayerFadeOut(%this);
      else
         %this.Stop();

      if (%this.IsPlaylistLoaded() && %pos <= 0.0 && %this.NextTrackIndex > 0)
      {
         %this.NextTrackIndex--;
         %this.CurrentTrackIndex = %this.NextTracks.GetAt(%this.NextTrackIndex);
      }
      
      if (%this.IsPlaylistLoaded())
         VMPlayerPlay(%this, %this.CurrentPlaylist.Tracks.GetAt(%this.CurrentTrackIndex));
      else
         VMPlayerPlay(%this, %song);
      
      if (%was_paused)
         %this.Pause();
         
      return true;
   }
   
   return false;
}


///////////////////////////////////////////////////////////////////////////////
// SkipForward
//
// Skips forward to the end of the song, or if a playlist is loaded and the
// currently playing song is not the last track then it will skip to the next
// track.  If no song is currently playing it returns false, otherwise true.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayer::SkipForward(%this)
{
   if (%this.IsPlaying() || %this.IsPaused())
   {
      if (%this.IsPlaylistLoaded())
      {
         %musicplayer.NextTrackPause = false;
         VMPlayerScheduleNextTrack(%this);
         return true;
      }
   }

   return false;
}



///////////////////////////////////////////////////////////////////////////////
// All functions below are private functions that should NEVER be called from
// outside the VMPlayer class!!!
///////////////////////////////////////////////////////////////////////////////


function VMPlayerScheduleNextTrack(%musicplayer)
{
   if (%musicplayer.IsPlaylistLoaded())
   {
      if (%musicplayer.NextTrackIndex + 1 < %musicplayer.NextTracks.GetCount())
         %musicplayer.NextTrackIndex++;
      else
         %musicplayer.NextTrackIndex = 0;

      if (%musicplayer.NextTrackIndex == 0 && !%musicplayer.IsRepeatEnabled())
      {
         %musicplayer.Stop();
         %musicplayer.CurrentTrackIndex = %musicplayer.NextTracks.GetAt(0);
         %musicplayer.NextTrackPause = false;
         return;
      }

      %musicplayer.CurrentTrackIndex = %musicplayer.NextTracks.GetAt(%musicplayer.NextTrackIndex);
   }

   %filename = %musicplayer.CurrentSong.Filename;
   %title = %musicplayer.CurrentSong.Title;
   %artist = %musicplayer.CurrentSong.Artist;
   %start_pos = %musicplayer.CurrentSong.StartPosition;
   %end_pos = %musicplayer.CurrentSong.EndPosition;

   %was_paused = (%musicplayer.NextTrackPause || %musicplayer.IsPaused());

   if (%musicplayer.IsCrossFadeEnabled() && (%musicplayer.IsPlaying() || %musicplayer.IsPaused()))
      VMPlayerCrossFadeOut(%musicplayer);
   else
      %musicplayer.Stop();

   if (%musicplayer.IsPlaylistLoaded())
      VMPlayerPlay(%musicplayer, %musicplayer.CurrentPlaylist.Tracks.GetAt(%musicplayer.NextTracks.GetAt(%musicplayer.NextTrackIndex)));
   else if (%musicplayer.IsRepeatEnabled())
      %musicplayer.PlaySong(%filename, %title, %artist, %start_pos, %end_pos);

   if (%was_paused)
      %musicplayer.Pause();

   %musicplayer.NextTrackPause = false;
   %musicplayer.NextTrackHandle = 0;
}


function VMPlayerNextTrack(%musicplayer)
{
   %musicplayer.NextTrackPause = false;
   %musicplayer.NextTrackHandle = schedule(%musicplayer.NextTrackDelayMS, 0, VMPlayerScheduleNextTrack, %musicplayer);
}


function VMPlayerCheckSongPosition(%musicplayer)
{
   if (!isObject(%musicplayer.CurrentSong))
      return;

   if (alxIsPaused(%musicplayer.CurrentMusicChannel.AudioHandle))
   {
      %musicplayer.CheckSongPosHandle = schedule($VMPlayerCheckSongPosIntervalMS, 0, VMPlayerCheckSongPosition, %musicplayer);
      return;
   }

   VMPlayerPrintDebugMessage(%musicplayer, "Song position "@%musicplayer.GetSongPosition()@" of "@%musicplayer.GetSongDuration());

   // Retrieve song position
   %pos = %musicplayer.GetSongPosition();
      
   // See if we have a negative position, it's because
   // we probably changed the start position while we
   // were playing the song, so we need to catch back up.
   if (%musicplayer.Streaming == true)
   {  
	  if (%pos < 0)
	  {
		 %musicplayer.PreviousPosition = -1;
		 %musicplayer.SetSongPosition(0);
		 %pos = 0;
	  }
	
	  // Check to see if we have any events that need to fire
	  if (%pos != %musicplayer.PreviousPosition)
	  {
		 for (%x = 0; %x < %musicplayer.CurrentSong.Events.GetCount(); %x++) 
		 {         
			// To make sure we only fire the event once we check against
			// both the current pos and last pos.
			if (%musicplayer.CurrentSong.Events.GetAt(%x).Position > (%musicplayer.CurrentSong.StartPosition + %musicplayer.PreviousPosition) && %musicplayer.CurrentSong.Events.GetAt(%x).Position <= (%musicplayer.CurrentSong.StartPosition + %pos) && %musicplayer.CurrentSong.StartPosition <= %musicplayer.CurrentSong.Events.GetAt(%x).Position)
			{
			   VMPlayerPrintDebugMessage(%musicplayer, "Firing event function");
			   schedule(1, 0, %musicplayer.CurrentSong.Events.GetAt(%x).EventFunction);
		    }
		 }
	  }
   
      %musicplayer.PreviousPosition = %pos;
   }

   if (alxIsPlaying(%musicplayer.CurrentMusicChannel.AudioHandle))
   {
      %end_pos = %musicplayer.GetSongDuration();

      // Events, fading, crossfading doesn't work on non-streaming files.
      if (%musicplayer.Streaming == true)
      {
         // See if we need to switch tracks
         if (%pos >= %musicplayer.GetSongDuration())
         {
            %musicplayer.Stop();
            VMPlayerNextTrack(%musicplayer);
            return;
         }

         // See if we need to fade/cross fade
         if (%musicplayer.IsPlaylistLoaded() && %musicplayer.IsCrossFadeEnabled() && (%musicplayer.NextTrackIndex + 1 < %musicplayer.NextTracks.GetCount() || %musicplayer.IsRepeatEnabled()) && %end_pos - %pos <= %musicplayer.FadeDuration)
         {
            %musicplayer.NextTrackPause = false;
            VMPlayerScheduleNextTrack(%musicplayer);
            return;
         }
         else if (%musicplayer.IsFadeEnabled() && %end_pos - %pos <= %musicplayer.FadeDuration)
         {
            %musicplayer.NextTrackPause = false;
            VMPlayerScheduleNextTrack(%musicplayer);
            return;
         }
      }
      
      %musicplayer.CheckSongPosHandle = schedule($VMPlayerCheckSongPosIntervalMS, 0, VMPlayerCheckSongPosition, %musicplayer);
   }
   else
   {
      // Since we aren't playing we DEFINATELY need to switch tracks
      %musicplayer.Stop();
      %musicplayer.NextTrackPause = false;
      VMPlayerNextTrack(%musicplayer);
   }
}


function VMPlayerDeleteTrackInfo(%musicplayer, %track_info)
{
   if (isObject(%track_info))
   {
      if (!%musicplayer.IsPlaylistLoaded())
      {
         %track_info.Events.delete();
         %track_info.delete();
      }
   }
}


function VMPlayerCopyTrackInfo(%musicplayer, %track_info)
{
   if (isObject(%track_info))
   {
      %events = new ScriptObject()
      {
         class = VMPArray;
      };

      for (%x = 0; %x < %track_info.Events.GetCount(); %x++)
      {
         %event = new ScriptObject()
         {
            class = VMPlayerEvent;
            Position = %track_info.Events.GetAt(%x).Position;
            EventFunction = %track_info.Events.GetAt(%x).EventFunction;
         };
         %events.InsertBack(%event);
      }

      // Add a new song to our playlist
      %track = new ScriptObject()
      {
         class = VMPlayerTrack;
         Filename = %track_info.Filename;
         Title = %track_info.Title;
         Artist = %track_info.Artist;
         StartPosition = %track_info.StartPosition;
         EndPosition = %track_info.EndPosition;
         Events = %events;
      };

      return %track;
   }
   
   return 0;
}


function VMPlayerGenerateTrackInfo(%filename, %title, %artist, %start_pos, %end_pos)
{
   if (%title $= "")
   {
      %index = 0;
      while (strpos(%filename, "\\", %index) != -1)
         %index = strpos(%filename, "\\", %index) + 1;

      %title = getSubStr(%filename, %index, strlen(%filename));

      %index = 0;
      while (strpos(%title, "/", %index) != -1)
         %index = strpos(%title, "/", %index) + 1;

      %title = getSubStr(%title, %index, strlen(%title));

      %offset = 0;
      %index = strlen(%title);
      while (strpos(%title, ".", %offset) != -1)
      {
         %index = strpos(%title, ".", %offset);
         %offset = %index + 1;
      }
      
      %title = getSubStr(%title, 0, %index);
   }

   if (%artist $= "")
      %artist = "Unknown";

   if (%start_pos $= "" || %start_pos < 0.0)
      %start_pos = 0.0;

   if (%end_pos $= "" || %end_pos < %start_pos)
      %end_pos = -1;

   %events = new ScriptObject()
   {
      class = VMPArray;
   };

   // Add a new song to our playlist
   %track = new ScriptObject()
   {
      class = VMPlayerTrack;
      Filename = %filename;
      Title = %title;
      Artist = %artist;
      StartPosition = %start_pos;
      EndPosition = %end_pos;
      Events = %events;
   };

   return %track;
}


function VMPlayerGetPlaylist(%musicplayer, %playlistname)
{
   if (%musicplayer.Playlists.GetCount() == 0 || %playlistname $= "")
      return 0;

   // Find the correct playlist
   for (%x = 0; %x < %musicplayer.Playlists.GetCount(); %x++) 
   {
      if (%musicplayer.Playlists.GetAt(%x).Name $= %playlistname)
         return %musicplayer.Playlists.GetAt(%x);
   }

   return 0;
}


function VMPlayerGetTrack(%musicplayer, %playlistname, %tracknumber)
{
   %playlist = VMPlayerGetPlaylist(%musicplayer, %playlistname);

   if (!isObject(%playlist))
      return 0;

   if (%tracknumber < 1 || %tracknumber > %playlist.Tracks.GetCount())
      return 0;

   // Return track
   return %playlist.Tracks.GetAt(%tracknumber - 1);
}


function VMPlayerGenerateNextTrackArray(%musicplayer)
{
   %musicplayer.NextTracks.RemoveAll();

   for (%x = %musicplayer.GetCurrentTrackNumber() - 1; %x < %musicplayer.GetTrackCount(%musicplayer.GetCurrentPlaylistName()); %x++)
      %musicplayer.NextTracks.InsertBack(%x);
   for (%x = 0; %x < %musicplayer.GetCurrentTrackNumber() - 1; %x++)
      %musicplayer.NextTracks.InsertBack(%x);

   if (%musicplayer.IsRandomEnabled())
   {
      // We need to randomize the next track list
      for (%x = 0; %x < %musicplayer.NextTracks.GetCount(); %x++)
      {
         %rnd = getRandom(0, %musicplayer.NextTracks.GetCount() - 1);
         %musicplayer.NextTracks.Swap(%x, %rnd);
      }
   }
}


function VMPlayerPlay(%musicplayer, %song)
{
   alxSetChannelVolume(%musicplayer.CurrentMusicChannel.AudioChannel, %musicplayer.CurrentVolume);
   %musicplayer.CurrentMusicChannel.AudioProfile.filename = %song.Filename;
   %musicplayer.CurrentMusicChannel.AudioHandle = alxPlay(%musicplayer.CurrentMusicChannel.AudioProfile);

   if (%song.StartPosition > 0.0)
      alxSeekToStreamPosition(%musicplayer.CurrentMusicChannel.AudioHandle, %song.StartPosition);

   if (alxIsPlaying(%musicplayer.CurrentMusicChannel.AudioHandle))
   {
      if (%song.EndPosition == -1)
         %song.EndPosition = alxGetStreamDuration(%musicplayer.CurrentMusicChannel.AudioHandle);

      %musicplayer.PreviousPosition = -1;
      
      if (%musicplayer.IsFadeEnabled())
      {
         // We are set to fade, so we've got to start fading in
         // from nothing.
         alxSetChannelVolume(%musicplayer.CurrentMusicChannel.AudioChannel, 0.0);

         %volume_delta = %musicplayer.CurrentVolume / ((%musicplayer.FadeDuration * 1000) / $VMPlayerFadeIntervalMS);
         VMPlayerScheduleMusicFadeIn(%musicplayer, %volume_delta, %musicplayer.CurrentMusicChannel);
      }
      else if (%musicplayer.IsCrossFadeEnabled() && %musicplayer.CrossFadeOutHandle != 0)
      {
         // We are set to cross fade, so we've got to start fading in
         // from nothing.
         alxSetChannelVolume(%musicplayer.CurrentMusicChannel.AudioChannel, 0.0);

         %volume_delta = %musicplayer.CurrentVolume / ((%musicplayer.FadeDuration * 1000) / $VMPlayerFadeIntervalMS);
         VMPlayerScheduleMusicCrossFadeIn(%musicplayer, %volume_delta, %musicplayer.CurrentMusicChannel);
      }
      else
      {
         // Make sure the volume is the correct level
         alxSetChannelVolume(%musicplayer.CurrentMusicChannel.AudioChannel, %musicplayer.CurrentVolume);
      }

      if (%musicplayer.FadeOutHandle != 0)
      {
         // Another channel is fading out and so we must wait until that
         // channel is done before playing so we'll just pause.
         alxPause(%musicplayer.CurrentMusicChannel.AudioHandle);
      }

      %musicplayer.CurrentSong = %song;
      %musicplayer.CheckSongPosHandle = schedule($VMPlayerCheckSongPosIntervalMS, 0, VMPlayerCheckSongPosition, %musicplayer);

      VMPlayerPrintDebugMessage(%musicplayer, "Playing song '"@%song.Filename@"'");

      return true;
   }

   VMPlayerPrintDebugMessage(%musicplayer, "Unable to play song '"@%song.Filename@"', filename does not exist or audio file is not valid");

   return false;
}


///////////////////////////////////////////////////////////////////////////////
// VMPlayerStop
//
// This is a private function that stops the alll music on both channels.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayerStop(%musicplayer)
{
   cancel(%musicplayer.CheckSongPosHandle);
   cancel(%musicplayer.NextTrackHandle);

   VMPlayerStopChannel(%musicplayer, %musicplayer.PrimaryMusicChannel);
   VMPlayerStopChannel(%musicplayer, %musicplayer.SecondaryMusicChannel);

   VMPlayerDeleteTrackInfo(%musicplayer, %musicplayer.CurrentSong);
   %musicplayer.CurrentSong = 0;
   
   %musicplayer.PreviousPosition = -1;
}


///////////////////////////////////////////////////////////////////////////////
// VMPlayerStopChannel
//
// This is a private function that stops the specified channel from playing
// music.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayerStopChannel(%musicplayer, %musicchannel)
{
   if (%musicchannel.AudioHandle != 0)
      alxStop(%musicchannel.AudioHandle);
   %musicchannel.AudioHandle = 0;
   
   alxSetChannelVolume(%musicchannel.AudioChannel, %musicplayer.CurrentVolume);
}


///////////////////////////////////////////////////////////////////////////////
// VMPlayerGetOtherMusicChannel
//
// This is a private function that returns the NON-CURRENT music channel.  This
// is used for both fade and cross fades, so one channel can be set to play
// music after the current channel completely fades out.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayerGetOtherMusicChannel(%musicplayer, %musicchannel)
{
   if (%musicchannel == %musicplayer.PrimaryMusicChannel)
      return %musicplayer.SecondaryMusicChannel;

   return %musicplayer.PrimaryMusicChannel;
}


///////////////////////////////////////////////////////////////////////////////
// VMPlayerSwitchMusicChannels
//
// This is a private function that changes between audio channels.  It's used
// for both fade and cross fades, so one channel can fade out while another
// channel fades in.
//
///////////////////////////////////////////////////////////////////////////////
function VMPlayerSwitchAudioChannels(%musicplayer)
{
   if (%musicplayer.CurrentMusicChannel == %musicplayer.PrimaryMusicChannel)
      %musicplayer.CurrentMusicChannel = %musicplayer.SecondaryMusicChannel;
   else
      %musicplayer.CurrentMusicChannel = %musicplayer.PrimaryMusicChannel;
}


function VMPlayerScheduleMusicFadeIn(%musicplayer, %volume_delta, %musicchannel)
{
   if (!VMPlayerDoMusicFade(%musicplayer, %volume_delta, %musicchannel))
   {
      %musicplayer.FadeInHandle = schedule($VMPlayerFadeIntervalMS, 0, VMPlayerScheduleMusicFadeIn, %musicplayer, %volume_delta, %musicchannel);
      return;
   }

   // Once we fade in completley we set our channel volume back to normal.
   alxSetChannelVolume(%musicchannel.AudioChannel, %musicplayer.CurrentVolume);
   %musicplayer.FadeInHandle = 0;
}


function VMPlayerFadeOut(%musicplayer, %musicchannel)
{
   VMPlayerDeleteTrackInfo(%musicplayer, %musicplayer.CurrentSong);
   %musicplayer.CurrentSong = 0;
   
   %curr_channel = %musicplayer.CurrentMusicChannel;
   %other_channel = VMPlayerGetOtherMusicChannel(%musicplayer, %musicplayer.CurrentMusicChannel);
   
   %curr_volume = alxGetChannelVolume(%curr_channel.AudioChannel);
   %other_volume = alxGetChannelVolume(%other_channel.AudioChannel);

   // If both channels are playing, we need to do some extra work.
   if (%other_channel.AudioHandle != 0)
   {
      if (%other_volume > %curr_volume)
      {
         // We want to fade out the OTHER channel
         VMPlayerStopChannel(%musicplayer, %curr_channel);
         VMPlayerSwitchAudioChannels(%musicplayer);
         %curr_volume = %other_volume;
      }
      else
      {
         // We want to fade out the CURRENT channel
         VMPlayerStopChannel(%musicplayer, %other_channel);
      }
   }
   
   cancel(%musicplayer.CheckSongPosHandle);
   cancel(%musicplayer.NextTrackHandle);
   cancel(%musicplayer.FadeInHandle);
   cancel(%musicplayer.FadeOutHandle);
   cancel(%musicplayer.CrossFadeOutHandle);
   cancel(%musicplayer.CrossFadeInHandle);
   
   %musicplayer.CheckSongPosHandle = 0;
   %musicplayer.NextTrackHandle = 0;
   %musicplayer.FadeInHandle = 0;
   %musicplayer.FadeOutHandle = 0;
   %musicplayer.CrossFadeOutHandle = 0;
   %musicplayer.CrossFadeInHandle = 0;
   
   %volume_delta = (%musicplayer.CurrentVolume / ((%musicplayer.FadeDuration * 1000) / $VMPlayerFadeIntervalMS)) * -1;

   VMPlayerScheduleMusicFadeOut(%musicplayer, %volume_delta, %musicplayer.CurrentMusicChannel);
   VMPlayerSwitchAudioChannels(%musicplayer);
}


function VMPlayerScheduleMusicFadeOut(%musicplayer, %volume_delta, %musicchannel)
{
   if (!VMPlayerDoMusicFade(%musicplayer, %volume_delta, %musicchannel))
   {
      %musicplayer.FadeOutHandle = schedule($VMPlayerFadeIntervalMS, 0, VMPlayerScheduleMusicFadeOut, %musicplayer, %volume_delta, %musicchannel);
      return;
   }

   // Once we fade out into nothing we can officially stop the music.
   if (%musicchannel.AudioHandle != 0)
      alxStop(%musicchannel.AudioHandle);
   %musicchannel.AudioHandle = 0;

   // Set our channel volume back to normal.
   alxSetChannelVolume(%musicchannel.AudioChannel, %musicplayer.CurrentVolume);
   %musicplayer.FadeOutHandle = 0;

   // Play the next song if we should be playing it.
   if (alxIsPaused(VMPlayerGetOtherMusicChannel(%musicplayer, %musicchannel).AudioHandle))
      alxUnpause(VMPlayerGetOtherMusicChannel(%musicplayer, %musicchannel).AudioHandle);
}


function VMPlayerScheduleMusicCrossFadeIn(%musicplayer, %volume_delta, %musicchannel)
{
   if (!VMPlayerDoMusicFade(%musicplayer, %volume_delta, %musicchannel))
   {
      %musicplayer.CrossFadeInHandle = schedule($VMPlayerFadeIntervalMS, 0, VMPlayerScheduleMusicCrossFadeIn, %musicplayer, %volume_delta, %musicchannel);
      return;
   }
   
   // Once we cross fades in completley we set our channel volume back to normal.
   alxSetChannelVolume(%musicchannel.AudioChannel, %musicplayer.CurrentVolume);
   %musicplayer.CrossFadeInHandle = 0;
}


function VMPlayerCrossFadeOut(%musicplayer)
{  
   VMPlayerDeleteTrackInfo(%musicplayer, %musicplayer.CurrentSong);
   %musicplayer.CurrentSong = 0;
   
   %curr_channel = %musicplayer.CurrentMusicChannel;
   %other_channel = VMPlayerGetOtherMusicChannel(%musicplayer, %musicplayer.CurrentMusicChannel);
   
   %curr_volume = alxGetChannelVolume(%curr_channel.AudioChannel);
   %other_volume = alxGetChannelVolume(%other_channel.AudioChannel);

   // If both channels are playing, we need to do some extra work.
   if (%other_channel.AudioHandle != 0)
   {
      if (%other_volume > %curr_volume)
      {
         // We want to fade out the OTHER channel
         cancel(%musicplayer.CheckSongPosHandle);
         cancel(%musicplayer.NextTrackHandle);
         VMPlayerStopChannel(%musicplayer, %curr_channel);
         VMPlayerSwitchAudioChannels(%musicplayer);
         %curr_volume = %other_volume;
      }
      else
      {
         // We want to fade out the CURRENT channel
         VMPlayerStopChannel(%musicplayer, %other_channel);
      }
   }
   
   cancel(%musicplayer.CheckSongPosHandle);
   cancel(%musicplayer.NextTrackHandle);
   cancel(%musicplayer.FadeInHandle);
   cancel(%musicplayer.FadeOutHandle);
   cancel(%musicplayer.CrossFadeOutHandle);
   cancel(%musicplayer.CrossFadeInHandle);
   
   %musicplayer.CheckSongPosHandle = 0;
   %musicplayer.NextTrackHandle = 0;
   %musicplayer.FadeInHandle = 0;
   %musicplayer.FadeOutHandle = 0;
   %musicplayer.CrossFadeOutHandle = 0;
   %musicplayer.CrossFadeInHandle = 0;
   
   // We are cross fading, so we want to play the next song
   // while we fade out the current song.
   %volume_delta = (%curr_volume / ((%musicplayer.FadeDuration * 1000) / $VMPlayerFadeIntervalMS)) * -1;
   VMPlayerScheduleMusicCrossFadeOut(%musicplayer, %volume_delta, %musicplayer.CurrentMusicChannel);
   VMPlayerSwitchAudioChannels(%musicplayer);
}


function VMPlayerScheduleMusicCrossFadeOut(%musicplayer, %volume_delta, %musicchannel)
{
   if (!VMPlayerDoMusicFade(%musicplayer, %volume_delta, %musicchannel))
   {
      %musicplayer.CrossFadeOutHandle = schedule($VMPlayerFadeIntervalMS, 0, VMPlayerScheduleMusicCrossFadeOut, %musicplayer, %volume_delta, %musicchannel);
      return;
   }

   // Once we fade out into nothing we can officially stop the music.
   if (%musicchannel.AudioHandle != 0)
      alxStop(%musicchannel.AudioHandle);
   %musicchannel.AudioHandle = 0;

   // Set our channel volume back to normal.
   alxSetChannelVolume(%musicchannel.AudioChannel, %musicplayer.CurrentVolume);
   %musicplayer.CrossFadeOutHandle = 0;
}


function VMPlayerDoMusicFade(%musicplayer, %volume_delta, %musicchannel)
{
   if (!alxIsPlaying(%musicchannel.AudioHandle) && !alxIsPaused(%musicchannel.AudioHandle))
   {
      // If somehow our music was manually stopped from the outside
      // (ie, via alxStop()) then we need to stop the fade and reset
      // our volume back to normal.
      alxSetChannelVolume(%musicchannel.AudioChannel, %musicplayer.CurrentVolume);
      return true;
   }

   if (alxIsPlaying(%musicchannel.AudioHandle))
   {
      %fade_volume = alxGetChannelVolume(%musicchannel.AudioChannel);

      if (%volume_delta < 0.0 && %fade_volume + %volume_delta <= 0)
         return true;
      else if (%volume_delta > 0.0 && %fade_volume + %volume_delta >= %musicplayer.CurrentVolume)
         return true;

      alxSetChannelVolume(%musicchannel.AudioChannel, %fade_volume + %volume_delta);
   }

   return false;
}


function VMPlayerPrintDebugMessage(%musicplayer, %msg)
{
   if (%musicplayer.Debug == true)
   {
      echo("VMPlayer: " @ %msg);
   }
}


///////////////////////////////////////////////////////////////////////////////
// The following VMPArray class is a very simple array wrapper.  It allows
// VMPlayer to easily manipulate arrays (ie, inserting/removing objects in the
// middle of an array) without having to write all the grunt work each time.
///////////////////////////////////////////////////////////////////////////////

function VMPArray::GetCount(%this)
{
   if (%this.Count $= "")
      %this.Count = 0;

   return %this.Count;
}


function VMPArray::InsertAt(%this, %index, %value)
{
   if (%index < 0 || %index > %this.GetCount())
      return;

   for (%x = %this.GetCount(); %x > %index; %x--)
   {
      %this.Content[%x] = %this.Content[%x - 1];
   }

   %this.Content[%index] = %value;
   %this.Count++;
}


function VMPArray::InsertBack(%this, %value)
{
   %index = %this.GetCount();

   %this.Content[%index] = %value;
   %this.Count++;
}


function VMPArray::RemoveAt(%this, %index)
{
   if (%index < 0 || %index >= %this.GetCount())
      return;

   for (%x = %index; %x < %this.GetCount(); %x++)
   {
      if (%x + 1 < %this.GetCount())
         %this.Content[%x] = %this.Content[%x + 1];
   }

   %this.Content[%this.Count - 1] = "";
   %this.Count--;
}


function VMPArray::RemoveBack(%this)
{
   if (%this.GetCount() > 0)
   {
      %this.Count--;
      %this.Content[%this.Count] = "";
   }
}


function VMPArray::RemoveAll(%this)
{
   for (%x = 0; %x < %this.GetCount(); %x++)
   {
      %this.Content[%x] = "";
   }
 
  %this.Count = 0;
}


function VMPArray::GetAt(%this, %index)
{
   if (%index >= 0 && %index < %this.GetCount())
   {
      return %this.Content[%index];
   }

   return "";
}


function VMPArray::IsEmpty(%this)
{
   return (%this.GetCount() == 0);
}


function VMPArray::Find(%this, %value)
{
   for (%x = 0; %x < %this.GetCount(); %x++)
   {
      if (%this.Content[%x] == %value)
         return %x;
   }

   return -1;
}


function VMPArray::FindString(%this, %value)
{
   for (%x = 0; %x < %this.GetCount(); %x++)
   {
      if (%this.Content[%x] $= %value)
         return %x;
   }

   return -1;
}


function VMPArray::Swap(%this, %index1, %index2)
{
   if (%index1 == %index2)
      return;

   if (%index1 < 0 || %index2 < 0 || %index1 >= %this.GetCount() || %index2 >= %this.GetCount())
      return;

   %tmp = %this.Content[%index2];
   %this.Content[%index2] = %this.Content[%index1];
   %this.Content[%index1] = %tmp;
}





