// NeL - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// 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 "std3d.h"
#include "nel/misc/string_mapper.h"
#include "nel/3d/ps_sound.h"
#include "nel/3d/particle_system.h"
#include "nel/3d/u_ps_sound_interface.h"
#include "nel/3d/ps_attrib_maker.h"
namespace NL3D
{
// we batch computation of Gains and frequencies. Here is the buffer size:
static const uint SoundBufSize = 1024;
// ***************************************************************************************************
CPSSound::CPSSound() : _Gain(1.f),
_GainScheme(NULL),
_Pitch(1.f),
_PitchScheme(NULL),
_EmissionPercent(1),
_SpawnSounds(false),
_Mute(false),
_SoundStopped(false),
_SoundReactivated(false),
_UseOriginalPitch(false)
{
NL_PS_FUNC(CPSSound_CPSSound)
if (CParticleSystem::getSerializeIdentifierFlag()) _Name = std::string("sound");
_SoundName = NLMISC::CSheetId::Unknown /*NLMISC::CStringMapper::emptyId()*/;
}
// ***************************************************************************************************
void CPSSound::stopSound()
{
NL_PS_FUNC(CPSSound_stopSound)
_SoundReactivated = false;
if (_SoundStopped) return;
CPSAttrib::iterator it = _Sounds.begin()
, endIt = _Sounds.end();
while (it != endIt)
{
if (*it)
{
(*it)->setLooping(false);
(*it)->release();
(*it) = NULL;
}
++it;
}
_SoundStopped = true;
}
// ***************************************************************************************************
void CPSSound::reactivateSound()
{
NL_PS_FUNC(CPSSound_reactivateSound)
//if (!_SoundStopped) return;
_SoundReactivated = true;
}
// ***************************************************************************************************
void CPSSound::removeAllSources(void)
{
NL_PS_FUNC(CPSSound_removeAllSources)
const sint32 size = _Sounds.getSize();
// delete all sounds, and rebuild them all
for (sint32 k = size - 1; k >= 0; --k)
{
deleteElement(k);
}
}
// ***************************************************************************************************
CPSSound::~CPSSound()
{
NL_PS_FUNC(CPSSound_CPSSound)
removeAllSources();
delete _GainScheme;
delete _PitchScheme;
}
// ***************************************************************************************************
uint32 CPSSound::getType(void) const
{
NL_PS_FUNC(CPSSound_getType)
return PSSound;
}
// ***************************************************************************************************
void CPSSound::step(TPSProcessPass pass)
{
NL_PS_FUNC(CPSSound_step)
if (pass != PSMotion) return;
const uint32 size = _Owner->getSize();
if (!size) return;
if (_SoundStopped && !_SoundReactivated)
{
return;
}
if (_SoundReactivated)
{
_SoundStopped = false;
_SoundReactivated = false;
if (!_Mute)
{
sint32 k;
// delete all sounds, and rebuild them all
removeAllSources();
for (k = 0; k < (sint32) size; ++k)
{
CPSEmitterInfo ei;
ei.setDefaults();
newElement(ei);
}
}
// don't need to reupdate sound
return;
}
nlassert(_Owner);
uint32 toProcess, leftToDo = size;
float Gains[SoundBufSize];
float frequencies[SoundBufSize];
uint GainPtInc = _GainScheme ? 1 : 0;
uint pitchPtInc = _PitchScheme ? 1 : 0;
float *currVol, *currPitch;
CPSAttrib::iterator it = _Sounds.begin(),
endIt;
CPSAttrib::const_iterator posIt = _Owner->getPos().begin();
CPSAttrib::const_iterator speedIt = _Owner->getSpeed().begin();
do
{
toProcess = leftToDo > SoundBufSize ? SoundBufSize : leftToDo;
// compute Gain
currVol = _GainScheme ? (float *) _GainScheme->make(getOwner(), size - leftToDo, Gains, sizeof(float), toProcess, true)
: &_Gain;
if (!_UseOriginalPitch)
{
// compute pitch
currPitch = _PitchScheme ? (float *) _PitchScheme->make(getOwner(), size - leftToDo, frequencies, sizeof(float), toProcess, true)
: &_Pitch;
endIt = it + toProcess;
const CMatrix &localToWorld = _Owner->getLocalToWorldMatrix();
do
{
if (*it) // was this sound instanciated?
{
(*it)->setSoundParams(*currVol,
localToWorld * *posIt,
localToWorld.mulVector(*speedIt),
*currPitch);
if ((*it)->isLooping() && !(*it)->isPlaying())
{
// looping sources do not play when they are clipped, so "reminds" the source to play when possible.
(*it)->play();
}
}
currVol += GainPtInc;
currPitch += pitchPtInc;
++posIt;
++speedIt;
++it;
}
while (it != endIt);
}
else
{
// keep original pitch
endIt = it + toProcess;
const CMatrix &localToWorld = _Owner->getLocalToWorldMatrix();
do
{
if (*it) // was this sound instanciated?
{
(*it)->setSoundParams(*currVol,
localToWorld * *posIt,
localToWorld.mulVector(*speedIt),
(*it)->getPitch());
}
currVol += GainPtInc;
++posIt;
++speedIt;
++it;
}
while (it != endIt);
}
leftToDo -= toProcess;
}
while (leftToDo);
}
// ***************************************************************************************************
void CPSSound::setGain(float Gain)
{
NL_PS_FUNC(CPSSound_setGain)
delete _GainScheme;
_GainScheme = NULL;
_Gain = Gain;
}
// ***************************************************************************************************
void CPSSound::setGainScheme(CPSAttribMaker *Gain)
{
NL_PS_FUNC(CPSSound_setGainScheme)
delete _GainScheme;
_GainScheme = Gain;
if (_Owner)
{
if (_GainScheme && _GainScheme->hasMemory()) _GainScheme->resize(_Owner->getMaxSize(), _Owner->getSize());
}
}
// ***************************************************************************************************
void CPSSound::setPitch(float pitch)
{
NL_PS_FUNC(CPSSound_setPitch)
delete _PitchScheme;
_PitchScheme = NULL;
_Pitch = pitch;
}
// ***************************************************************************************************
void CPSSound::setPitchScheme(CPSAttribMaker *pitch)
{
NL_PS_FUNC(CPSSound_setPitchScheme)
delete _PitchScheme;
_PitchScheme = pitch;
if (_Owner)
{
if (_PitchScheme && _PitchScheme->hasMemory()) _PitchScheme->resize(_Owner->getMaxSize(), _Owner->getSize());
}
}
// ***************************************************************************************************
void CPSSound::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
{
NL_PS_FUNC(CPSSound_serial)
CPSLocatedBindable::serial(f);
// version 3 : added option to keep original pitch from the .sound
sint ver = f.serialVersion(3);
if (f.isReading())
{
std::string soundName;
f.serial(soundName);
_SoundName = NLMISC::CSheetId(soundName)/*NLMISC::CStringMapper::map(soundName)*/;
}
else
{
std::string soundName = _SoundName.toString()/*NLMISC::CStringMapper::unmap(_SoundName)*/;
f.serial(soundName);
}
sint32 nbSounds;
bool hasScheme;
if (f.isReading())
{
f.serial(nbSounds); // we are very unlikely to save a system with sounds being played in it,
// but we need to keep datas coherency.
if (_Owner)
{
_Sounds.resize(_Owner->getMaxSize());
}
}
else
{
nbSounds = _Sounds.getSize(); // number of used sound
f.serial(nbSounds);
}
if (f.isReading())
{
delete _GainScheme;
_GainScheme = NULL;
delete _PitchScheme;
_PitchScheme = NULL;
}
// save Gain infos
hasScheme = _GainScheme != NULL;
f.serial(hasScheme);
if (hasScheme)
{
f.serialPolyPtr(_GainScheme);
}
else
{
f.serial(_Gain);
}
// serial pitch infos
if (ver >= 3)
{
bool useOriginalPitch = _UseOriginalPitch;
f.serial(useOriginalPitch);
_UseOriginalPitch = useOriginalPitch;
if (!_UseOriginalPitch)
{
// serialize pitch infos (no needed otherwise)
hasScheme = _PitchScheme != NULL;
f.serial(hasScheme);
if (hasScheme)
{
f.serialPolyPtr(_PitchScheme);
}
else
{
f.serial(_Pitch);
}
}
}
else
{
hasScheme = _PitchScheme != NULL;
f.serial(hasScheme);
if (hasScheme)
{
f.serialPolyPtr(_PitchScheme);
}
else
{
f.serial(_Pitch);
}
}
if (ver > 1)
{
f.serial(_EmissionPercent);
f.serial(_SpawnSounds);
}
if (f.isReading())
{
_SoundStopped = true;
// insert blank sources
for (sint k = 0; k < nbSounds; ++k)
{
CPSEmitterInfo ei;
ei.setDefaults();
newElement(ei);
}
_SoundStopped = false;
_SoundReactivated = true;
}
}
// ***************************************************************************************************
void CPSSound::newElement(const CPSEmitterInfo &info)
{
NL_PS_FUNC(CPSSound_newElement)
nlassert(_Owner);
if (_GainScheme && _GainScheme->hasMemory()) _GainScheme->newElement(info);
if (_PitchScheme && _PitchScheme->hasMemory()) _PitchScheme->newElement(info);
// if there's a sound server, we generate a new sound instance
if (!_Mute && !_SoundStopped && CParticleSystem::getSoundServer())
{
if ((rand() % 99) * 0.01f < _EmissionPercent)
{
uint32 index = _Sounds.insert(CParticleSystem::getSoundServer()->createSound(_SoundName, _SpawnSounds));
/// set position and activate the sound
if (_Sounds[index])
{
const NLMISC::CMatrix &mat = getLocalToWorldMatrix();
float pitch;
if (_UseOriginalPitch)
{
pitch = _Sounds[index]->getPitch();
}
else
{
pitch = _PitchScheme ? _PitchScheme->get(getOwner(), 0) : _Pitch;
}
_Sounds[index]->setSoundParams(_GainScheme ? _GainScheme->get(getOwner(), 0) : _Gain,
mat * _Owner->getPos()[index],
_Owner->getSpeed()[index],
pitch
);
_Sounds[index]->play();
}
}
else
{
_Sounds.insert(NULL);
}
}
else
{
_Sounds.insert(NULL);
}
}
// ***************************************************************************************************
void CPSSound::deleteElement(uint32 index)
{
NL_PS_FUNC(CPSSound_deleteElement)
if (_GainScheme && _GainScheme->hasMemory()) _GainScheme->deleteElement(index);
if (_PitchScheme && _PitchScheme->hasMemory()) _PitchScheme->deleteElement(index);
if (_Sounds[index])
{
_Sounds[index]->setLooping(false);
_Sounds[index]->release();
}
_Sounds.remove(index);
}
// ***************************************************************************************************
void CPSSound::resize(uint32 size)
{
NL_PS_FUNC(CPSSound_resize)
nlassert(size < (1 << 16));
if (_GainScheme && _GainScheme->hasMemory()) _GainScheme->resize(size, getOwner()->getSize());
if (_PitchScheme && _PitchScheme->hasMemory()) _PitchScheme->resize(size, getOwner()->getSize());
if (size < _Sounds.getSize())
{
// if vector size has been shrunk, must delete sounds instances
for(uint k = size; k < _Sounds.getSize(); ++k)
{
if (_Sounds[k])
{
_Sounds[k]->setLooping(false);
_Sounds[k]->release();
}
}
}
_Sounds.resize(size);
}
// ***************************************************************************************************
void CPSSound::setUseOriginalPitchFlag(bool useOriginalPitch)
{
NL_PS_FUNC(CPSSound_setUseOriginalPitchFlag)
if (_PitchScheme)
{
delete _PitchScheme;
_PitchScheme = NULL;
}
_UseOriginalPitch = useOriginalPitch;
}
} // NL3D