// 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