// NeL - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This source file has been modified by the following contributors:
// Copyright (C) 2010 Matt RAYKOWSKI (sfb)
// Copyright (C) 2012-2019 Jan BOON (Kaetemi)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#include "stdsound.h"
#include "nel/sound/complex_source.h"
#include "nel/sound/complex_sound.h"
using namespace std;
using namespace NLMISC;
namespace NLSOUND
{
CComplexSource::CComplexSource (CComplexSound *soundPattern, bool spawn, TSpawnEndCallback cb, void *cbUserParam, NL3D::CCluster *cluster, CGroupController *groupController)
: CSourceCommon(soundPattern, spawn, cb, cbUserParam, cluster, groupController),
_Source1(NULL),
_Source2(NULL),
_Muted(false)
{
nlassert(soundPattern->getSoundType() == CSound::SOUND_COMPLEX);
_PatternSound = static_cast(soundPattern);
// read original parameters
_Gain = soundPattern->getGain();
_Pitch = soundPattern->getPitch();
_Looping = soundPattern->getLooping();
_Priority = soundPattern->getPriority();
_TickPerSecond = soundPattern->getTicksPerSecond();
}
CComplexSource::~CComplexSource()
{
// CAudioMixerUser *mixer = CAudioMixerUser::instance();
// security
CAudioMixerUser::instance()->unregisterUpdate(this);
CAudioMixerUser::instance()->removeEvents(this);
std::vector::iterator first(_AllSources.begin()), last(_AllSources.end());
for (; first != last; ++first)
{
//mixer->removeSource(*first);
delete *first;
}
}
TSoundId CComplexSource::getSound()
{
return _PatternSound;
}
/*
void CComplexSource::setPriority( TSoundPriority pr, bool redispatch)
{
}
void CComplexSource:: setLooping( bool l )
{
}
bool CComplexSource::getLooping() const
{
return false;
}
*/
bool CComplexSource::isPlaying()
{
return _Playing;
}
void CComplexSource::play()
{
if (_Gain == 0)
{
_Muted = true;
}
else
{
playStuf();
}
CSourceCommon::play();
}
void CComplexSource::playStuf()
{
CAudioMixerUser *mixer = CAudioMixerUser::instance();
NLMISC::TTime now = NLMISC::CTime::getLocalTime();
switch (_PatternSound->getPatternMode())
{
case CComplexSound::MODE_CHAINED:
{
_SoundSeqIndex = 0;
_FadeFactor = 1.0f;
const vector &soundSeq = _PatternSound->getSoundSeq();
if (!soundSeq.empty())
{
CSound *sound = mixer->getSoundId(_PatternSound->getSound(soundSeq[_SoundSeqIndex++]));
if (sound == 0)
return;
if (_PatternSound->doFadeIn())
_FadeLength = min(uint32(_PatternSound->getFadeLength()/_TickPerSecond), sound->getDuration() /2);
else
_FadeLength = 0;
_Source2 = mixer->createSource(sound, false, 0, 0, _Cluster, NULL, _GroupController);
if (_Source2 == NULL)
return;
_Source2->setPriority(_Priority);
_Source2->setRelativeGain(0);
_Source2->setPos(_Position);
_Source2->setPitch(_Source2->getSound()->getPitch() * _Pitch);
_Source2->play();
_StartTime2 = now;
// register for fade in.
mixer->registerUpdate(this);
}
}
break;
case CComplexSound::MODE_SPARSE:
{
// use Source1, sound sequence, delay sequence and event.
_SoundSeqIndex = 0;
_DelaySeqIndex = 0;
const std::vector &delaySeq = _PatternSound->getDelaySeq();
if (!delaySeq.empty() && delaySeq[_DelaySeqIndex] != 0)
{
_LastSparseEvent = false;
// begin with a delay
mixer->addEvent(this, uint64(now + delaySeq[_DelaySeqIndex++]/_TickPerSecond));
}
else
{
if (!delaySeq.empty())
_DelaySeqIndex = 1;
const vector &soundSeq = _PatternSound->getSoundSeq();
if (!soundSeq.empty())
{
CSound *sound = mixer->getSoundId(_PatternSound->getSound(soundSeq[_SoundSeqIndex++]));
_Source1 = mixer->createSource(sound, false, 0, 0, _Cluster, NULL, _GroupController);
if (_Source1 == NULL)
return;
_Source1->setPriority(_Priority);
_Source1->setRelativeGain(_Gain*_Gain*_Gain);
_Source1->setPos(_Position);
_Source1->setPitch(_Source1->getSound()->getPitch() * _Pitch);
_Source1->play();
_StartTime1 = now;
// register event for next sound.
const std::vector &delaySeq = _PatternSound->getDelaySeq();
if (!delaySeq.empty() && _DelaySeqIndex < delaySeq.size())
{
// event for next sound.
mixer->addEvent(this, uint64(now + sound->getDuration() + delaySeq[_DelaySeqIndex++]/_TickPerSecond));
if (_DelaySeqIndex >= delaySeq.size() && !_Looping)
_LastSparseEvent = true;
else
_LastSparseEvent = false;
}
else
{
_LastSparseEvent = true;
// event for stop
mixer->addEvent(this, now + sound->getDuration());
}
}
}
}
break;
case CComplexSound::MODE_ALL_IN_ONE:
{
// just spanw all the listed source.
const std::vector &sounds = _PatternSound->getSounds();
std::vector::const_iterator first(sounds.begin()), last(sounds.end());
if (_AllSources.empty())
{
// create the sources
for (; first != last; ++first)
{
CSound *sound = mixer->getSoundId(*first);
if (sound != NULL)
{
USource *source = mixer->createSource(sound, false, 0, 0, _Cluster, NULL, _GroupController);
if (source != NULL)
{
source->setPriority(_Priority);
source->setRelativeGain(_Gain*_Gain*_Gain);
source->setPos(_Position);
source->setPitch(source->getSound()->getPitch() * _Pitch);
source->play();
_AllSources.push_back(source);
}
}
}
}
else
{
// just replay the existing source.
std::vector::iterator first(_AllSources.begin()), last(_AllSources.end());
for (; first != last; ++first)
{
(*first)->setRelativeGain(_Gain*_Gain*_Gain);
(*first)->setPos(_Position);
(*first)->setPitch((*first)->getSound()->getPitch() * _Pitch);
(*first)->play();
}
}
if (!_Looping)
{
// event to stop the sound
mixer->addEvent(this, NLMISC::CTime::getLocalTime() + _PatternSound->getDuration());
}
}
break;
default:
nldebug("Unknow pattern mode. Can't play.");
}
_Muted = false;
}
void CComplexSource::stop()
{
CAudioMixerUser *mixer = CAudioMixerUser::instance();
if (_Source1)
{
// _Source1->stop();
// mixer->removeSource(_Source1);
delete _Source1;
_Source1 = NULL;
}
if (_Source2)
{
// _Source2->stop();
// mixer->removeSource(_Source2);
delete _Source2;
_Source2 = NULL;
}
std::vector::iterator first(_AllSources.begin()), last(_AllSources.end());
for (; first != last; ++first)
{
if ((*first)->isPlaying())
(*first)->stop();
delete *first;
}
_AllSources.clear();
switch (_PatternSound->getPatternMode())
{
case CComplexSound::MODE_CHAINED:
mixer->unregisterUpdate(this);
mixer->removeEvents(this);
break;
case CComplexSound::MODE_SPARSE:
case CComplexSound::MODE_ALL_IN_ONE:
mixer->removeEvents(this);
break;
case CComplexSound::MODE_UNDEFINED:
default:
break;
}
CSourceCommon::stop();
}
/*void CComplexSource::unregisterSpawnCallBack()
{
}
*/
void CComplexSource::setPos( const NLMISC::CVector& pos )
{
CSourceCommon::setPos(pos);
if (_Source1 != NULL)
_Source1->setPos(pos);
if (_Source2 != NULL)
_Source2->setPos(pos);
std::vector::iterator first(_AllSources.begin()), last(_AllSources.end());
for (; first != last; ++first)
{
(*first)->setPos(pos);
}
}
void CComplexSource::setVelocity( const NLMISC::CVector& vel )
{
CSourceCommon::setVelocity(vel);
if (_Source1 != NULL)
_Source1->setVelocity(vel);
if (_Source2 != NULL)
_Source2->setVelocity(vel);
std::vector::iterator first(_AllSources.begin()), last(_AllSources.end());
for (; first != last; ++first)
{
(*first)->setVelocity(vel);
}
}
/*void CComplexSource::getVelocity( NLMISC::CVector& vel ) const
{
}
*/
void CComplexSource::setDirection( const NLMISC::CVector& dir )
{
CSourceCommon::setDirection(dir);
if (_Source1 != NULL)
_Source1->setDirection(dir);
if (_Source2 != NULL)
_Source2->setDirection(dir);
std::vector::iterator first(_AllSources.begin()), last(_AllSources.end());
for (; first != last; ++first)
{
(*first)->setDirection(dir);
}
}
/*
void CComplexSource::getDirection( NLMISC::CVector& dir ) const
{
}
*/
void CComplexSource::setGain( float gain )
{
CSourceCommon::setGain(gain);
// update the gain of the played source.
if (_PatternSound->getPatternMode() == CComplexSound::MODE_CHAINED)
{
// set sub source volume with fade value.
if (_Source1 != NULL)
_Source1->setRelativeGain((1.0f - _FadeFactor) * _Gain*_Gain*_Gain);
if (_Source2 != NULL)
_Source2->setRelativeGain(_FadeFactor * _Gain*_Gain*_Gain);
}
else
{
if (_Source1 != NULL)
_Source1->setRelativeGain(_Gain*_Gain*_Gain);
if (_Source2 != NULL)
_Source2->setRelativeGain(_Gain*_Gain*_Gain);
}
std::vector::iterator first(_AllSources.begin()), last(_AllSources.end());
for (; first != last; ++first)
{
(*first)->setGain(_Gain);
}
if (_Muted && _Playing)
playStuf();
}
void CComplexSource::setRelativeGain( float gain )
{
CSourceCommon::setRelativeGain(gain);
// update the gain of the played source.
if (_PatternSound->getPatternMode() == CComplexSound::MODE_CHAINED)
{
// set sub source volume with fade value.
if (_Source1 != NULL)
_Source1->setRelativeGain((1.0f - _FadeFactor) * _Gain*_Gain*_Gain);
if (_Source2 != NULL)
_Source2->setRelativeGain(_FadeFactor * _Gain*_Gain*_Gain);
}
else
{
if (_Source1 != NULL)
_Source1->setRelativeGain(_Gain*_Gain*_Gain);
if (_Source2 != NULL)
_Source2->setRelativeGain(_Gain*_Gain*_Gain);
}
std::vector::iterator first(_AllSources.begin()), last(_AllSources.end());
for (; first != last; ++first)
{
(*first)->setRelativeGain(_Gain);
}
if (_Muted && _Playing)
playStuf();
}
/*
void CComplexSource::setPitch( float pitch )
{
}
float CComplexSource::getPitch() const
{
return 0;
}
*/
/*
void CComplexSource::setSourceRelativeMode( bool mode )
{
}
bool CComplexSource::getSourceRelativeMode() const
{
return false;
}
*/
uint32 CComplexSource::getTime()
{
// evaluate the elapsed time.
if (!_Playing || _PlayStart == 0) // not started ?
return 0;
TTime now = NLMISC::CTime::getLocalTime();
TTime delta = now - _PlayStart;
return uint32(delta);
}
/// Mixer update implementation.
void CComplexSource::onUpdate()
{
// do the cross fade :
// - lower sound1, louder sound2,
// - when max reach, stop the update, swap the sound, delete sound1 and set event for next fade.
// can only occur for chained mode.
nlassert(_PatternSound->getPatternMode() == CComplexSound::MODE_CHAINED);
CAudioMixerUser *mixer = CAudioMixerUser::instance();
// compute xfade factor.
TTime now = NLMISC::CTime::getLocalTime();
if (_FadeLength > 0)
{
_FadeFactor = float((double(now) - double(_StartTime2)) / double(_FadeLength)) ;
// _FadeFactor = (_FadeFactor*_FadeFactor);
}
else
_FadeFactor = 1.0f;
// nldebug("Fade factor = %f", _FadeFactor);
if (_FadeFactor >= 1.0)
{
_FadeFactor = 1.0f;
// fade end !
if (_Source1)
{
// _Source1->stop();
// mixer->removeSource(_Source1);
delete _Source1;
_Source1 = NULL;
}
if (_Source2)
{
// set max volume
_Source2->setRelativeGain(1.0f * _Gain);
// 'swap' the source
_Source1 = _Source2;
_FadeFactor = 0.0f;
_StartTime1 = _StartTime2;
_Source2 = NULL;
// if there is a next sound available, program an event for the next xfade.
CSound *sound2 = NULL;
// _SoundSeqIndex++;
const vector &soundSeq = _PatternSound->getSoundSeq();
if (_SoundSeqIndex < soundSeq.size())
{
sound2 = mixer->getSoundId(_PatternSound->getSound(soundSeq[_SoundSeqIndex++]));
}
else if (_Looping)
{
// restart the sound sequence
_SoundSeqIndex = 0;
sound2 = mixer->getSoundId(_PatternSound->getSound(soundSeq[_SoundSeqIndex++]));
}
if (sound2 != NULL)
{
//nldebug("CS : Chaining to sound %s", CStringMapper::unmap(sound2->getName()).c_str());
CAudioMixerUser *mixer = CAudioMixerUser::instance();
// determine the XFade length (if next sound is too short.
_FadeLength = minof(uint32(_PatternSound->getFadeLength()/_TickPerSecond), (sound2->getDuration()) / 2, (_Source1->getSound()->getDuration())/2);
_Source2 = mixer->createSource(sound2, false, 0, 0, _Cluster, NULL, _GroupController);
if (_Source2)
{
_Source2->setPriority(_Priority);
// there is a next sound, add event for xfade.
//nldebug("Seting event for sound %s in %u millisec (XFade = %u).", CStringMapper::unmap(_Source1->getSound()->getName()).c_str(), _Source1->getSound()->getDuration()-_FadeLength, _FadeLength);
mixer->addEvent(this, _StartTime1 + _Source1->getSound()->getDuration() - _FadeLength);
}
}
else
{
// no sound after, just set an event at end of current sound to stop the complex sound.
nldebug("Setting last event for sound %s in %u millisec.", CStringMapper::unmap(_Source1->getSound()->getName()).c_str(), _Source1->getSound()->getDuration());
if (_PatternSound->doFadeOut())
{
// set the event to begin fade out.
mixer->addEvent(this, _StartTime1 + _Source1->getSound()->getDuration() - _PatternSound->getFadeLength());
}
else
{
// set the event at end of sound.
mixer->addEvent(this, _StartTime1 + _Source1->getSound()->getDuration());
}
}
}
else
{
if (_PatternSound->doFadeOut())
{
// update is responsible for stoping the sound.
_Playing = false;
}
}
// remove from the update list
mixer->unregisterUpdate(this);
}
else
{
// nldebug("XFade : %4.3f <=> %4.3f (Fade Len = %6.3f", (1.0f-_FadeFactor)*_Gain, _FadeFactor*_Gain, _FadeLength/1000.0f);
// do the xfade
if (_Source1)
{
// lower the sound 1.
_Source1->setRelativeGain((1.0f - _FadeFactor) * _Gain);
}
if (_Source2)
{
// lower the sound 1.
// _Source2->setRelativeGain(float(sqrt(_FadeFactor)) * _Gain);
_Source2->setRelativeGain(_FadeFactor * _Gain);
}
}
}
/// Mixer event implementation.
void CComplexSource::onEvent()
{
CAudioMixerUser *mixer = CAudioMixerUser::instance();
NLMISC::TTime now = NLMISC::CTime::getLocalTime();
switch (_PatternSound->getPatternMode())
{
case CComplexSound::MODE_CHAINED:
{
// either it's time to begin a new xfade, or to end this sound.
if (_Source2 != NULL)
{
// start new cross fade.?
_StartTime2 = now;
// mute the source2
_Source2->setRelativeGain(0);
_Source2->setPitch(_Source2->getSound()->getPitch() * _Pitch);
_Source2->setPos(_Position);
// start the source 2
_Source2->play();
// register for update.
mixer->registerUpdate(this);
}
else
{
if (_PatternSound->doFadeOut())
{
// set in update list for fade out.
_StartTime2 = now;
mixer->registerUpdate(this);
}
else
{
// end the sound.
// _Source1->stop();
// mixer->removeSource(_Source1);
delete _Source1;
_Source1 = NULL;
_Playing = false;
}
}
}
break;
case CComplexSound::MODE_SPARSE:
{
if (_Source1 != NULL)
{
// _Source1->stop();
// mixer->removeSource(_Source1);
delete _Source1;
_Source1 = NULL;
}
const std::vector &delaySeq = _PatternSound->getDelaySeq();
const vector &soundSeq = _PatternSound->getSoundSeq();
if (_Looping && _DelaySeqIndex >= delaySeq.size())
{
_DelaySeqIndex = 1;
/* if (!delaySeq.empty() && delaySeq[0] == 0)
_DelaySeqIndex = 1;
else
_DelaySeqIndex = 0;
*/ }
if (!soundSeq.empty() && !_LastSparseEvent)
{
// wrap around sound sequence until there are delays...
if (_SoundSeqIndex >= soundSeq.size())
_SoundSeqIndex = 0;
CSound *sound = mixer->getSoundId(_PatternSound->getSound(soundSeq[_SoundSeqIndex++]));
_Source1 = mixer->createSource(sound, false, 0, 0, _Cluster, NULL, _GroupController);
if (_Source1 == NULL)
{
stop();
return;
}
_Source1->setPriority(_Priority);
_Source1->setRelativeGain(_Gain*_Gain*_Gain);
_Source1->setPitch(_Source1->getSound()->getPitch() * _Pitch);
_Source1->setPos(_Position);
_Source1->play();
_StartTime1 = now;
// register event for next sound.
if (!delaySeq.empty() && _DelaySeqIndex < delaySeq.size())
{
// event for next sound.
mixer->addEvent(this, uint64(now + sound->getDuration() + delaySeq[_DelaySeqIndex++]/_TickPerSecond));
if (_DelaySeqIndex == delaySeq.size() && !_Looping)
_LastSparseEvent = true;
}
else
{
// event for stop ?
if (!_Looping)
_LastSparseEvent = true;
else
_LastSparseEvent = false;
mixer->addEvent(this, now + sound->getDuration());
}
}
else
{
// this is the event for stop !
stop();
}
}
break;
case CComplexSound::MODE_ALL_IN_ONE:
// just call the stop method.
stop();
break;
default:
nlassert(false);
}
}
void CComplexSource::checkup()
{
if (_Muted)
return;
if (_Source1 != NULL && _Source1->getSound()->getLooping() && !_Source1->isPlaying())
_Source1->play();
if (_Source2 != NULL && _Source2->getSound()->getLooping() && !_Source2->isPlaying())
_Source2->play();
std::vector::iterator first(_AllSources.begin()), last(_AllSources.end());
for (; first != last; ++first)
{
USource *source = *first;
if (source == NULL)
continue;
if (source->getSound()->getLooping() && !source->isPlaying())
source->play();
if (source->getSound()->getSoundType() != CSound::SOUND_SIMPLE)
static_cast(source)->checkup();
}
}
} // NLSOUND