/*
MixBox v0.2.0

Copyright Jonathan Dearborn 2009

See MixBox.h for more info and license.
*/
#include "MixBox.h"
#include <cmath>

namespace MB
{

MixBox MixBox::Instance;


/*
Sets the audio format.  The mixer must be reopened for this to take effect.
MIX_DEFAULT_FORMAT, AUDIO_U8, AUDIO_S8, AUDIO_U16LSB, AUDIO_S16LSB, AUDIO_U16MSB, AUDIO_S16MSB, AUDIO_U16, AUDIO_S16, AUDIO_U16SYS, AUDIO_S16SYS
MIX_DEFAULT_FORMAT is the same as AUDIO_S16SYS.
*/
void MixBox::setAudioFormat(Uint16 format)
{
    audio_format = format;
}

/*
Returns:
    Uint16 - The audio format.
*/
Uint16 MixBox::getAudioFormat()
{
    return audio_format;
}

/*
Sets the audio playback rate in bytes per second.  The mixer must be reopened for this to take effect.
A common rate for games is 22050.  CD quality is 44100.
*/
void MixBox::setAudioRate(unsigned int rate)
{
    audio_rate = rate;
}

/*
Returns:
    unsigned int - The audio playback rate in bytes per second.
*/
unsigned int MixBox::getAudioRate()
{
    return audio_rate;
}

/*
Sets the number of output (not mixing) channels.  The mixer must be reopened for this to take effect.
1 for mono, 2 for stereo, etc.
*/
void MixBox::setNumOutputs(unsigned int num)
{
    audio_channels = num;
}

/*
Returns:
    unsigned int - The number of output (not mixing) channels.
*/
unsigned int MixBox::getNumOutputs()
{
    return audio_channels;
}

/*
Sets the size of the audio buffer in bytes.  The mixer must be reopened for this to take effect.
*/
void MixBox::setBufferSize(unsigned int size)
{
    audio_buffers = size;
}

/*
Returns:
    unsigned int - The size of the audio buffer in bytes.
*/
unsigned int MixBox::getBufferSize()
{
    return audio_buffers;
}

/*
Initializes SDL's audio subsystem, opens SDL_mixer, and gets the number of available mixing channels.
Side Effects:
    Reports error messages to stderr.
Returns:
    true on success
    false on failure
*/
bool MixBox::openMixer()
{
    #ifndef MIXBOX_NO_SDL
    // From my quick tests, I can call init SDL's audio even if it's already up, without getting failure.
    if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
    {
        fprintf(stderr, "MixBox: Unable to initialize audio: %s\n", SDL_GetError());
        return false;
    }
    #endif
    
    if (Mix_OpenAudio(audio_rate, audio_format, audio_channels, audio_buffers) != 0)
    {
        fprintf(stderr, "MixBox: Unable to open mixer: %s\n", Mix_GetError());
        return false;
    }
    
    mixerIsOpen = true;
    
    // Get the current number of mixing channels
    numChannels = Mix_AllocateChannels(-1);
    
    // Prepare the effect flags for each channel
    while(numChannels > channelData.size())
        channelData.push_back(ChannelData(channelData.size()));
    
    return true;
}

/*
Closes SDL_mixer and zeros the number of available mixing channels.
*/
void MixBox::closeMixer()
{
    mixerIsOpen = false;
    Mix_CloseAudio();
    numChannels = 0;
    channelData.clear();
}


/*
Sets the number of available mixing channels.
Returns:
    The number of available mixing channels.  A possible success condition is
    whether the return value equals the input value.
*/
unsigned int MixBox::setNumChannels(unsigned int numChannels)
{
    this->numChannels = Mix_AllocateChannels(numChannels);
    
    // Delete unneeded effect flags
    if(this->numChannels < channelData.size())
    {
        std::vector<ChannelData>::iterator e = channelData.begin();
        for(unsigned int i = 0; i < this->numChannels; i++)
            e++;
        channelData.erase(e, channelData.end());
    }
    
    // Allocate more effect flags if needed
    while(this->numChannels > channelData.size())
        channelData.push_back(ChannelData(channelData.size()));
    return this->numChannels;
}

/*
Returns:
    The number of available mixing channels.
*/
unsigned int MixBox::getNumChannels() const
{
    return numChannels;
}


/*
Switches the stereo output's left and right signals.
Returns:
    true on success
    false on failure
*/
bool MixBox::swapStereo()
{
    stereoSwap = !stereoSwap;
    return (Mix_SetReverseStereo(MIX_CHANNEL_POST, stereoSwap) != 0);
    /*bool result = false;
    for(unsigned int i = 0; i < numChannels; i++)
    {
        if(Mix_SetReverseStereo(i, stereoSwap) == 0)
            result = false;
    }
    return result;*/
}

/*
Returns:
    true if the stereo output's left and right signals have been switched.
    false if the stereo output's left and right signals have not been switched.
*/
bool MixBox::isStereoSwapped() const
{
    return stereoSwap;
}

/*
Loads sound data from a file.
Returns:
    SoundID of the new sound.  If this SoundID's getID() returns -1, then there was an error.
*/
SoundID MixBox::loadSound(const char* filename)
{
    Mix_Chunk* sound = Mix_LoadWAV(filename);
    if(sound == NULL)
        return SoundID(-1);

    sounds.push_back(sound);

    return SoundID(sounds.size() - 1);
}


/*
Opens a music file for playing.
Returns:
    MusicID of the new music.  If this MusicID's getID() returns -1, then there was an error.
*/
MusicID MixBox::loadMusic(const char* filename)
{
    Mix_Music* music = Mix_LoadMUS(filename);
    if(music == NULL)
        return MusicID(-1);

    musics.push_back(music);

    return MusicID(musics.size() - 1);
}


/*
Deletes the memory of each held sound and clears MixBox's sound container.
*/
void MixBox::freeSounds()
{
    for(unsigned int i = 0; i < sounds.size(); i++)
    {
        Mix_FreeChunk(sounds[i]);
    }

    sounds.clear();
}

/*
Deletes the memory of the given sound.
*/
void MixBox::free(const SoundID& id)
{
    if(id.isGood())
    {
        Mix_FreeChunk(sounds[id.getID()]);
        sounds[id.getID()] = NULL;
    }
}

/*
Deletes the memory of each held music and clears MixBox's music container.
*/
void MixBox::freeMusic()
{
    for(unsigned int i = 0; i < musics.size(); i++)
    {
        Mix_FreeMusic(musics[i]);
    }

    musics.clear();
}

/*
Deletes the memory of the given music.
*/
void MixBox::free(const MusicID& id)
{
    if(id.isGood())
    {
        Mix_FreeMusic(musics[id.getID()]);
        musics[id.getID()] = NULL;
    }
}

/*
Plays the given sound on an available channel.
Returns:
    ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
*/
ChannelID MixBox::play(const SoundID& id, int loops)
{
    if(id.isBad())
        return -1;

    int channel = Mix_PlayChannel(-1, sounds[id.getID()], loops);
    
    // Automatically remove effects that interfere with normal sounds.
    bool goodChannel = (channel >= 0 && channel < int(channelData.size()));
    if(goodChannel)
        channelData[channel].removeStandardEffects();

    return channel;
}

/*
Plays the given sound on the given channel.
*/
void MixBox::play(const SoundID& sound, const ChannelID& channel, int loops)
{
    if(sound.isBad() || channel.isBad())
        return;

    Mix_PlayChannel(channel.getID(), sounds[sound.getID()], loops);
    
    // Automatically remove effects that interfere with normal sounds.
    bool goodChannel = (channel.getID() >= 0 && channel.getID() < int(channelData.size()));
    if(goodChannel)
        channelData[channel.getID()].removeStandardEffects();
}

// PRIVATE
/*
Plays the given sound on the given channel with the volume controlled by the distance.
Returns:
    ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
*/
ChannelID MixBox::playDistance(const SoundID& id, int channel, float distance, float maxRange, int loops)
{
    // Adjust the volume according to distance
    distance = maxRange - distance;
    if(distance > 0)
    {
        // Scale to range (0, 128]
        distance = MIX_MAX_VOLUME*(distance/maxRange);
        // Get the volume
        Uint8 vol = distance > MIX_MAX_VOLUME? MIX_MAX_VOLUME : distance;
        Mix_Chunk* sound = sounds[id.getID()];
        //Mix_VolumeChunk(sound, vol);
        
        channel = Mix_PlayChannel(channel, sound, loops);
        
        // Automatically remove effects that interfere with normal sounds.
        bool goodChannel = (channel >= 0 && channel < int(channelData.size()));
        bool hasVolumeEffect = channelData[channel].hasEffect(ChannelData::EFFECT_VOLUME);
        if(goodChannel)
            channelData[channel].removeStandardEffects();
        if(goodChannel && !hasVolumeEffect)
        {
            channelData[channel].addEffect(ChannelData::EFFECT_VOLUME);
            channelData[channel].oldVolume = Mix_Volume(channel, -1);
        }
        Mix_Volume(channel, vol);
        
        // Reset volume for next time
        //Mix_VolumeChunk(sound, 128);
    }

    return ChannelID(channel);
}

// PRIVATE
/*
Plays the given sound on the given channel with the volume controlled by the distance and panning controlled by the direction.
Returns:
    ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
*/
ChannelID MixBox::playPanDistance(const SoundID& id, int channel, float distance, float maxRange, float direction, int loops)
{
    // Adjust the volume according to distance
    distance = maxRange - distance;
    if(distance > 0)
    {
        direction += 2*M_PI;
        
        // Scale to range (0, 128]
        distance = 255*(distance/maxRange);
        // Get the volume
        Uint8 vol = distance > 255? 255 : distance;
        vol = 255 - vol;
        Mix_Chunk* sound = sounds[id.getID()];
        
        channel = Mix_PlayChannel(channel, sound, loops);
        
        // Automatically remove effects that interfere with normal sounds.
        bool goodChannel = (channel >= 0 && channel < int(channelData.size()));
        if(goodChannel)
            channelData[channel].removeStandardEffects();
        Mix_SetPosition(channel, direction * 180.0f/M_PI, vol);
        if(goodChannel)
            channelData[channel].addEffect(ChannelData::EFFECT_POSITION);
    }

    return ChannelID(channel);
}



// PRIVATE
/*
Plays the given sound on the given channel with the volume controlled by the distance and panning controlled by the direction.
Returns:
    ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
*/
ChannelID MixBox::playPan2dDistance(const SoundID& id, int channel, float distance, float maxRange, float direction, int loops)
{
    // Adjust the volume according to distance
    distance = maxRange - distance;
    if(distance > 0)
    {
        // Direction is from -1.0 (left) to 1.0 (right)
        
        // Scale to range (0, 255]
        distance = 255*(distance/maxRange);
        // Get the volume
        Uint8 vol = distance > 255? 255 : distance;
        //vol = 255 - vol;
        
        int left = vol;
        int right = vol;
        if(direction > 0)
            left = vol*(1 - direction);
        else if(direction < 0)
            right = vol*(1 + direction);
        
        if(left < 0)
            left = 0;
        else if(left > 254)
            left = 254;

        if(right < 0)
            right = 0;
        else if(right > 254)
            right = 254;
        
        Mix_Chunk* sound = sounds[id.getID()];
        
        channel = Mix_PlayChannel(channel, sound, loops);
        
        
        // Automatically remove effects that interfere with normal sounds.
        bool goodChannel = (channel >= 0 && channel < int(channelData.size()));
        if(goodChannel)
            channelData[channel].removeStandardEffects();
        //Mix_SetPosition(channel, direction * 180.0f/M_PI, vol);
        Mix_SetPanning(channel, left, right);
        if(goodChannel)
            channelData[channel].addEffect(ChannelData::EFFECT_PANNING);
    }

    return ChannelID(channel);
}

/*
Plays the given sound on an available channel with the volume controlled by the distance from the origin.
Returns:
    ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
*/
ChannelID MixBox::playVolume2D(const SoundID& id, float x, float y, float maxRange, int loops)
{
    if(id.isBad() || maxRange <= 0)
        return -1;

    return playDistance(id, -1, sqrt(x*x + y*y), maxRange, loops);
}

/*
Plays the given sound on an available channel with the volume controlled by the distance from the origin.
Returns:
    ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
*/
ChannelID MixBox::playVolume3D(const SoundID& id, float x, float y, float z, float maxRange, int loops)
{
    if(id.isBad() || maxRange <= 0)
        return -1;

    return playDistance(id, -1, sqrt(x*x + y*y + z*z), maxRange, loops);
}

/*
Plays the given sound on an available channel with the volume controlled by the distance and panning controlled by the direction from the origin.
Returns:
    ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
*/
ChannelID MixBox::playPan2D(const SoundID& id, float x, float y, float maxRange, int loops)
{
    if(id.isBad() || maxRange <= 0)
        return -1;
    
    /*float direction = atan2(y, x);
    if(direction > 3*M_PI/4 || direction < -3*M_PI/4)
        direction = M_PI + M_PI/2;  // Left
    else if(direction < M_PI/4 && direction > -M_PI/4)
        direction = M_PI/2;  // Right
    else
        direction = 0;  // Front*/
    float direction = x/maxRange;
    if(direction < -1.0f)
        direction = -1.0f;
    else if(direction > 1.0f)
        direction = 1.0f;

    return playPan2dDistance(id, -1, sqrt(x*x + y*y), maxRange, direction, loops);
}

/*
Plays the given sound on an available channel with the volume controlled by the distance and panning controlled by the direction from the origin.
Returns:
    ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
*/
ChannelID MixBox::playPan3D(const SoundID& id, float x, float y, float z, float maxRange, int loops)
{
    if(id.isBad() || maxRange <= 0)
        return -1;
    
    float direction = atan2(y, x);

    return playPanDistance(id, -1, sqrt(x*x + y*y + z*z), maxRange, direction, loops);
}

/*
Plays the given sound on the given channel with the volume controlled by the distance from the origin.
*/
void MixBox::playVolume2D(const SoundID& id, const ChannelID& channel, float x, float y, float maxRange, int loops)
{
    if(id.isBad() || channel.isBad() || maxRange <= 0)
        return;

    playDistance(id, channel.getID(), sqrt(x*x + y*y), maxRange, loops);
}

/*
Plays the given sound on the given channel with the volume controlled by the distance from the origin.
*/
void MixBox::playVolume3D(const SoundID& id, const ChannelID& channel, float x, float y, float z, float maxRange, int loops)
{
    if(id.isBad() || channel.isBad() || maxRange <= 0)
        return;

    playDistance(id, channel.getID(), sqrt(x*x + y*y + z*z), maxRange, loops);
}

/*
Plays the given sound on the given channel with the volume controlled by the distance and panning controlled by the direction from the origin.
*/
void MixBox::playPan2D(const SoundID& id, const ChannelID& channel, float x, float y, float maxRange, int loops)
{
    if(id.isBad() || channel.isBad() || maxRange <= 0)
        return;
    
    /*float direction = atan2(y, x);
    if(direction > 3*M_PI/4 || direction < -3*M_PI/4)
        direction = M_PI + M_PI/2;  // Left
    else if(direction < M_PI/4 && direction > -M_PI/4)
        direction = M_PI/2;  // Right
    else
        direction = 0;  // Front*/
    float direction = x/maxRange;
    if(direction < -1.0f)
        direction = -1.0f;
    else if(direction > 1.0f)
        direction = 1.0f;

    playPan2dDistance(id, channel.getID(), sqrt(x*x + y*y), maxRange, direction, loops);
}

/*
Plays the given sound on the given channel with the volume controlled by the distance and panning controlled by the direction from the origin.
*/
void MixBox::playPan3D(const SoundID& id, const ChannelID& channel, float x, float y, float z, float maxRange, int loops)
{
    if(id.isBad() || channel.isBad() || maxRange <= 0)
        return;
    
    float direction = atan2(y, x);
    
    playPanDistance(id, channel.getID(), sqrt(x*x + y*y + z*z), maxRange, direction, loops);
}

/*
Removes all MixBox effects from the given channel.
*/
void MixBox::clearEffects(const ChannelID& id)
{
    if(id.isBad())
        return;
    
    /*Mix_SetPosition(id.getID(), 0, 0);
    Mix_Volume(id.getID(), MIX_MAX_VOLUME);
    Mix_SetPanning(id.getID(), 255, 255);
    Mix_SetDistance(id.getID(), 0);*/
    
	bool goodChannel = (id.getID() >= 0 && id.getID() < int(channelData.size()));
	if(goodChannel)
		channelData[id.getID()].removeStandardEffects();
}


/*
Stops the current sounds playing on each channel.
*/
void MixBox::stop()
{
    for(unsigned int i = 0; i < numChannels; i++)
        Mix_HaltChannel(i);
}

/*
Stops the current sound playing on the given channel.
*/
void MixBox::stop(const ChannelID& id)
{
    if(id.isBad())
        return;

    Mix_HaltChannel(id.getID());
}

		
/*
Pauses the given channel.
*/
void MixBox::pause(const ChannelID& id)
{
    if(id.isBad())
        return;

    Mix_Pause(id.getID());
}
		
/*
Returns:
    true if the given channel is paused.
    false if the given channel is not paused.
*/
bool MixBox::isPaused(const ChannelID& id) const
{
    if(id.isBad())
        return false;
    
    return Mix_Paused(id.getID());
}

/*
Resumes the given channel when paused.
*/
void MixBox::resume(const ChannelID& id)
{
    if(id.isBad())
        return;

    Mix_Resume(id.getID());
}

/*
Plays music.
*/
void MixBox::play(const MusicID& id, int loops)
{
    if(id.isBad())
        return;

    Mix_PlayMusic(musics[id.getID()], loops);
}

/*
Returns:
    true if music is playing
    false if music is not playing
*/
bool MixBox::isMusicPlaying() const
{
    return Mix_PlayingMusic();
}

/*
Stops the current music.
*/
void MixBox::stopMusic()
{
    Mix_HaltMusic();
}


/*
Pauses the current music.
*/
void MixBox::pauseMusic()
{
    Mix_PauseMusic();
}
		
/*
Returns:
    true if the music is paused.
    false if the music is not paused.
*/
bool MixBox::isMusicPaused() const
{
    return Mix_PausedMusic();
}

/*
Resets the current music to the beginning of the file.
*/
void MixBox::rewindMusic()
{
    Mix_RewindMusic();
}

/*
Resumes play of music that had been paused.
*/
void MixBox::resumeMusic()
{
    Mix_ResumeMusic();
}

/*
Moves the playing position in the current music.
Returns:
    true on success
    false on failure
This can fail if the file format does not support skipping.
*/
bool MixBox::skipMusic(double position)
{
    return (Mix_SetMusicPosition(position) == 0);
}

/*
Begins playing music from the given skipPosition, fading in over the given time.
Returns:
    true on success
    false on failure
*/
bool MixBox::fadeInMusic(const MusicID& id, unsigned int milliseconds, double skipPosition, int loops)
{
    if(id.isBad())
        return false;
    return (Mix_FadeInMusicPos(musics[id.getID()], loops, milliseconds, skipPosition) == 0);
}

/*
Begins fading out the currently playing music over the given time.
Returns:
    true on success
    false on failure
*/
bool MixBox::fadeOutMusic(unsigned int milliseconds)
{
    return (Mix_FadeOutMusic(milliseconds) == 1);
}



/*
Sets the volume of the given channel in a range of 0 (silent) to 1 (max).
*/
void MixBox::setVolume(const ChannelID& id, float volume)
{
    if(id.isBad())
        return;
    if(volume < 0)
        volume = 0;
    else if(volume > 1.0f)
        volume = 1.0f;
    
    Uint8 vol = MIX_MAX_VOLUME*volume;
    if(vol > MIX_MAX_VOLUME)
        vol = MIX_MAX_VOLUME;
    Mix_Volume(id.getID(), vol);
    if(channelData[id.getID()].hasEffect(ChannelData::EFFECT_VOLUME))
        channelData[id.getID()].effects ^= ChannelData::EFFECT_VOLUME;
}

/*
Returns:
    float - The volume of the given channel in a range of 0 (silent) to 1 (max).
*/
float MixBox::getVolume(const ChannelID& id) const
{
    if(id.isBad())
        return -1;
    return Mix_Volume(id.getID(), -1)/MIX_MAX_VOLUME;
}



/*
Returns:
    true if any channel is playing
    false if no channels are playing
*/
bool MixBox::isPlaying() const
{
    for(unsigned int i = 0; i < numChannels; i++)
    {
        if(Mix_Playing(i) != 0)
            return true;
    }
    return false;
}

/*
Returns:
    true if the given channel is playing
    false if the given channel is not playing
*/
bool MixBox::isPlaying(const ChannelID& id) const
{
    if(id.isBad())
        return false;
    return (Mix_Playing(id.getID()) != 0);
}


/*
Returns:
    SoundID - The SoundID that represents the given index into the private container of sounds.
*/
SoundID MixBox::getSound(unsigned int index) const
{
    if(index >= sounds.size())
        return SoundID();
    return SoundID(index);
}

/*
Returns:
    MusicID - The MusicID that represents the given index into the private container of music.
*/
MusicID MixBox::getMusic(unsigned int index) const
{
    if(index >= musics.size())
        return MusicID();
    return MusicID(index);
}

/*
Returns:
    ChannelID - The ChannelID that represents the given channel index.
*/
ChannelID MixBox::getChannel(unsigned int index) const
{
    if(index >= numChannels)
        return ChannelID();
    return ChannelID(index);
}

/*
Returns:
    unsigned int - The number of sounds in the private sound container.
*/
unsigned int MixBox::getNumSounds() const
{
    return sounds.size();
}

/*
Returns:
    unsigned int - The number of music samples in the private music container.
*/
unsigned int MixBox::getNumMusic() const
{
    return musics.size();
}


// Pure virtual destructor...  Still needs to be defined. (?!)
MixID::~MixID()
{}


/*
Returns:
    true if the SoundID represents a valid sound.
    false if the SoundID does not represent a valid sound.
*/
bool SoundID::isGood() const
{
    MixBox& mixer = MixBox::instance();
    return !(id < 0 || id >= int(mixer.sounds.size()) || mixer.sounds[id] == NULL);
}

/*
Returns:
    true if the SoundID does not represent a valid sound.
    false if the SoundID represents a valid sound.
*/
bool SoundID::isBad() const
{
    MixBox& mixer = MixBox::instance();
    return (id < 0 || id >= int(mixer.sounds.size()) || mixer.sounds[id] == NULL);
}

/*
Sets the volume of the sound chunk.
*/
void SoundID::setVolume(float volume)
{
    MixBox& mixer = MixBox::instance();
    
    if(id < 0 || id >= int(mixer.sounds.size())
       || mixer.sounds[id] == NULL)
        return;
    if(volume < 0)
	volume = 0;
    else if(volume > 1.0f)
	volume = 1.0f;

    Uint8 vol = MIX_MAX_VOLUME*volume;
    if(vol > MIX_MAX_VOLUME)
	vol = MIX_MAX_VOLUME;
    Mix_VolumeChunk(mixer.sounds[id], vol);
}

/*
Returns:
    float - The volume of the sound in a range of 0 (silent) to 1 (max).
*/
float SoundID::getVolume() const
{
    MixBox& mixer = MixBox::instance();
    
    if(id < 0 || id >= int(mixer.sounds.size())
       || mixer.sounds[id] == NULL)
        return -1;
    
    return float(Mix_VolumeChunk(mixer.sounds[id], -1))/MIX_MAX_VOLUME;
}


/*
Plays the sound on an available channel.
Returns:
    ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
*/
ChannelID SoundID::play(int loops)
{
	return MixBox::instance().play(*this, loops);
}

/*
Plays the sound on the given channel.
*/
void SoundID::play(const ChannelID& channel, int loops)
{
	MixBox::instance().play(*this, channel, loops);
}

/*
Plays the sound on an available channel with the volume controlled by the distance from the origin.
Returns:
    ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
*/
ChannelID SoundID::playVolume2D(float x, float y, float maxRange, int loops)
{
	return MixBox::instance().playVolume2D(*this, x, y, maxRange, loops);
}

/*
Plays the sound on an available channel with the volume controlled by the distance from the origin.
Returns:
    ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
*/
ChannelID SoundID::playVolume3D(float x, float y, float z, float maxRange, int loops)
{
	return MixBox::instance().playVolume3D(*this, x, y, z, maxRange, loops);
}

/*
Plays the sound on an available channel with the volume controlled by the distance and panning controlled by the direction from the origin.
Returns:
    ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
*/
ChannelID SoundID::playPan2D(float x, float y, float maxRange, int loops)
{
	return MixBox::instance().playPan2D(*this, x, y, maxRange, loops);
}

/*
Plays the sound on an available channel with the volume controlled by the distance and panning controlled by the direction from the origin.
Returns:
    ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
*/
ChannelID SoundID::playPan3D(float x, float y, float z, float maxRange, int loops)
{
	return MixBox::instance().playPan3D(*this, x, y, z, maxRange, loops);
}

/*
Plays the sound on the given channel with the volume controlled by the distance from the origin.
*/
void SoundID::playVolume2D(const ChannelID& channel, float x, float y, float maxRange, int loops)
{
	MixBox::instance().playVolume2D(*this, channel, x, y, maxRange, loops);
}

/*
Plays the sound on the given channel with the volume controlled by the distance from the origin.
*/
void SoundID::playVolume3D(const ChannelID& channel, float x, float y, float z, float maxRange, int loops)
{
	MixBox::instance().playVolume3D(*this, channel, x, y, z, maxRange, loops);
}


/*
Plays the sound on the given channel with the volume controlled by the distance and panning controlled by the direction from the origin.
*/
void SoundID::playPan2D(const ChannelID& channel, float x, float y, float maxRange, int loops)
{
	MixBox::instance().playPan2D(*this, channel, x, y, maxRange, loops);
}

/*
Plays the sound on the given channel with the volume controlled by the distance and panning controlled by the direction from the origin.
*/
void SoundID::playPan3D(const ChannelID& channel, float x, float y, float z, float maxRange, int loops)
{
	MixBox::instance().playPan3D(*this, channel, x, y, z, maxRange, loops);
}


/*
Returns:
    true if the MusicID represents a valid music sample.
    false if the MusicID does not represent a valid music sample.
*/
bool MusicID::isGood() const
{
    MixBox& mixer = MixBox::instance();
    return !(id < 0 || id >= int(mixer.musics.size()) || mixer.musics[id] == NULL);
}

/*
Returns:
    true if the MusicID does not represent a valid music sample.
    false if the MusicID represents a valid music sample.
*/
bool MusicID::isBad() const
{
    MixBox& mixer = MixBox::instance();
    return (id < 0 || id >= int(mixer.musics.size()) || mixer.musics[id] == NULL);
}

/*
Sets the volume of the music sample in a range of 0 (silent) to 1 (max).
*/
void MusicID::setVolume(float volume)
{
    MixBox& mixer = MixBox::instance();
    
    
    if(id < 0 || id >= int(mixer.musics.size())
       || mixer.musics[id] == NULL)
        return;
    
    if(volume < 0)
	volume = 0;
    else if(volume > 1.0f)
	volume = 1.0f;

    Uint8 vol = MIX_MAX_VOLUME*volume;
    if(vol > MIX_MAX_VOLUME)
	vol = MIX_MAX_VOLUME;
    Mix_VolumeMusic(vol);
}

/*
Returns:
    float - The volume of the music sample in a range of 0 (silent) to 1 (max).
*/
float MusicID::getVolume() const
{
    MixBox& mixer = MixBox::instance();
    
    if(id < 0 || id >= int(mixer.musics.size())
       || mixer.musics[id] == NULL)
        return -1;
    
    return float(Mix_VolumeMusic(-1))/MIX_MAX_VOLUME;
}


/*
Plays the music.
*/
void MusicID::play(int loops)
{
    MixBox::instance().play(*this, loops);
}

/*
Returns:
    true if the ChannelID represents a valid channel.
    false if the ChannelID does not represent a valid channel.
*/
bool ChannelID::isGood() const
{
    MixBox& mixer = MixBox::instance();
    return !(id < 0 || id >= int(mixer.numChannels));
}

/*
Returns:
    true if the ChannelID does not represent a valid channel.
    false if the ChannelID represents a valid channel.
*/
bool ChannelID::isBad() const
{
    MixBox& mixer = MixBox::instance();
    return (id < 0 || id >= int(mixer.numChannels));
}


/*
Sets the volume of the channel in a range of 0 (silent) to 1 (max).
*/
void ChannelID::setVolume(float volume)
{
    MixBox::instance().setVolume(*this, volume);
}

/*
Returns:
    float - The volume of the channel in a range of 0 (silent) to 1 (max).
*/
float ChannelID::getVolume() const
{
    return MixBox::instance().getVolume(*this);
}

/*
Removes all MixBox effects from the channel.
*/
void ChannelID::clearEffects()
{
    MixBox::instance().clearEffects(*this);
}

/*
Returns:
    true if the channel is playing
    false if the channel is not playing
*/
bool ChannelID::isPlaying() const
{
    return MixBox::instance().isPlaying(*this);
}

/*
Plays the given sound on the channel.
*/
void ChannelID::play(const SoundID& sound, int loops)
{
    MixBox::instance().play(sound, *this, loops);
}

/*
Stops the current sound playing on the channel.
*/
void ChannelID::stop()
{
    MixBox::instance().stop(*this);
}

/*
Pauses the channel.
*/
void ChannelID::pause()
{
    MixBox::instance().pause(*this);
}
	
/*
Returns:
    true if the channel is paused.
    false if the channel is not paused.
*/
bool ChannelID::isPaused() const
{
    return MixBox::instance().isPaused(*this);
}

/*
Resumes the channel when paused.
*/
void ChannelID::resume()
{
    MixBox::instance().resume(*this);
}

/*
Plays the given sound on the channel with the volume controlled by the distance from the origin.
*/
void ChannelID::playVolume2D(const SoundID& id, float x, float y, float maxRange, int loops)
{
    MixBox::instance().playVolume2D(id, *this, x, y, maxRange, loops);
}

/*
Plays the given sound on the channel with the volume controlled by the distance from the origin.
*/
void ChannelID::playVolume3D(const SoundID& id, float x, float y, float z, float maxRange, int loops)
{
    MixBox::instance().playVolume3D(id, *this, x, y, z, maxRange, loops);
}

/*
Plays the given sound on the channel with the volume controlled by the distance and panning controlled by the direction from the origin.
*/
void ChannelID::playPan2D(const SoundID& id, float x, float y, float maxRange, int loops)
{
    MixBox::instance().playPan2D(id, *this, x, y, maxRange, loops);
}

/*
Plays the given sound on the channel with the volume controlled by the distance and panning controlled by the direction from the origin.
*/
void ChannelID::playPan3D(const SoundID& id, float x, float y, float z, float maxRange, int loops)
{
    MixBox::instance().playPan3D(id, *this, x, y, z, maxRange, loops);
}



}
