/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Sound class based on PLIB.
 * @verbatim
 * PLIB sound:
 * -----------
 * - To play sound, PLIB requires being pulsed (5 Hz minimum?).
 *   Otherwise, it will play a snippet of a sound file, then fade out.
 * - PLIB alters a sample's volume by modifying its waveform (slow operation).
 *   This Sound class avoids PLIB distorting the waveform by restoring
 *   the original waveform (so changing volume becomes even slower).
 * - PLIB will cause program exit if a sample is deleted while still playing.
 *   stopSample() and update() don't quite work according to the PLIB docs.
 *   The reliable way is to enqueue zombie samples then deleted them
 *   when their "play count" reaches zero.
 * @endverbatim
 *//*
 * LEGAL:   COPYRIGHT (C) 2007 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#if COMPILE_PLIB

#define SOUND_COMPILE_PLIB_CC 1
#include "base/module.hh"
#include "base/file.hh"
#include "base/timer.hh"
#include "base/conf.hh"
#include "base/stream.hh"
using namespace base;
#include "sound/module.hh"
#include "sound/conf.hh"
#include "sound/sound_plib.hh"
#include "sound/sound_sample_plib.hh"
// after base
#include <plib/sl.h>
#include <plib/sm.h>

namespace sound {

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////  SoundPLIB  ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

SoundPLIB* SoundPLIB::msInstance;

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
SoundPLIB::SoundPLIB( void )
:   SoundBase(),
    mEnabled(false),  // initially disabled
    mWasEnabled(false),
    mVolume(defs::DEFAULT_VOLUME),
    mTickFunctor(new SoundPLIB::TickFunctor),
    mSched(new slScheduler(defs::SOUND_PLIB_SAMPLE_RATE)),
    mMixer(new smMixer(defs::SOUND_PLIB_MIXER_DEVICE.c_str())),
    mZombieSamples()
{
    // NOP
}

SoundPLIB::~SoundPLIB()
{
#if SOUND_FREE_PLIB
    DELETE_NULL( mSched );
    DELETE_NULL( mMixer );
#endif
}

/*****************************************************************************
 * Enable/disable sound.
 *****************************************************************************/
bool
SoundPLIB::Enable( const bool enable )
{
    // Permanently disabled?
    if ( SOUND_CONF.mPermanentlyDisabled )
    {
        mEnabled = false;
        return true;
    }

    if ( enable and (not mEnabled) )  // going to enable
    {
        // Print once (unpausing can cause Enable(true) to be called again).
        if ( not mWasEnabled )
            CLOG << "Enabled PLIB sound." << std::endl;

        mEnabled = mWasEnabled = true;

        mMixer->setMasterVolume( defs::SOUND_PLIB_VOLUME );
        GET_TIMER().Register( mTickFunctor, defs::SOUND_PLIB_TICK_FREQ );
    }
    else if ( (not enable) and mEnabled )  // going to disable
    {
        mEnabled = false;

        mMixer->setMasterVolume( 0.0f );
        GET_TIMER().Unregister( mTickFunctor );
    }
    // else no change

    return mEnabled;
}

/*****************************************************************************
 * @return Master volume.
 *****************************************************************************/
fp
SoundPLIB::GetVolume( void )
{
    return mVolume;
}

/*****************************************************************************
 * Set volume (for all samples).
 *****************************************************************************/
bool
SoundPLIB::SetVolume( const fp volume )
{
    // (not really implemented)
    mVolume = volume;
    return true;
}

/*****************************************************************************
 * Create specific Sample object (this is called by SoundBase).
 *****************************************************************************/
shptr<Sample>
SoundPLIB::CreateSample( const Sample::Name& sampleName )
{
    return new SamplePLIB( sampleName );
}


/*****************************************************************************
 * Timer-tick pulses the plib sound system.
 *****************************************************************************/
void
SoundPLIB::TickFunctor::Tick( const Milliseconds millisecElapsed )
{
    SoundPLIB::GetInstance().Pulse();
}

void
SoundPLIB::Pulse( void )
{
    if ( not mEnabled )
        return;

    // Call PLIB to play next segment of waveform.
    mSched->update();

    // Destroy zombie samples after they're finished playing.
    // PLIB will exit program if sample were to be deleted while still playing.
    for ( std::set<slSample*>::iterator iter = mZombieSamples.begin();
          iter != mZombieSamples.end();
          /*NOP*/ )
    {
        slSample* sample = *iter;
        if ( sample->getPlayCount() == 0 )  // if done playing
        {
//#if SOUND_FREE_PLIB  // must delete object
            // Delete PLIB sample.
            delete sample;
//#endif
            // STL trick to delete node.
            std::set<slSample*>::iterator iter2 = iter;
            ++iter2;
            mZombieSamples.erase( iter );
            iter = iter2;
        }
        else
        {
            ++iter;
        }
    }
}

/*****************************************************************************
 * Enqueue a zombie sample (possibly still playing).
 *****************************************************************************/
void
SoundPLIB::EnqueueZombieSample( slSample* zombie )
{
    mZombieSamples.insert( zombie );
}

} // namespace sound

#endif // COMPILE_PLIB
