354 lines
8.5 KiB
C++
354 lines
8.5 KiB
C++
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
#include "stdsound.h"
|
|
#include "nel/sound/complex_sound.h"
|
|
#include "nel/misc/path.h"
|
|
#include "nel/misc/common.h"
|
|
#include "nel/sound/audio_mixer_user.h"
|
|
|
|
using namespace std;
|
|
using namespace NLMISC;
|
|
|
|
namespace NLSOUND
|
|
{
|
|
|
|
bool CComplexSound::isDetailed() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void CComplexSound::parseSequence(const std::string &str, std::vector<uint32> &seq, uint scale)
|
|
{
|
|
seq.clear();
|
|
|
|
uint32 tmp;
|
|
string val;
|
|
string::const_iterator first(str.begin()), last(str.end());
|
|
|
|
for (; first != last; ++first)
|
|
{
|
|
if (*first != ';')
|
|
val += *first;
|
|
else
|
|
{
|
|
fromString(val, tmp);
|
|
seq.push_back(tmp * scale);
|
|
val.clear();
|
|
}
|
|
}
|
|
|
|
// parse the last value
|
|
if (!val.empty())
|
|
{
|
|
fromString(val, tmp);
|
|
seq.push_back(tmp * scale);
|
|
}
|
|
|
|
}
|
|
|
|
void CComplexSound::getSubSoundList(std::vector<std::pair<std::string, CSound*> > &subsounds) const
|
|
{
|
|
CAudioMixerUser *mixer = CAudioMixerUser::instance();
|
|
std::vector<NLMISC::TStringId>::const_iterator first(_Sounds.begin()), last(_Sounds.end());
|
|
for (; first != last; ++first)
|
|
{
|
|
CSound *sound = mixer->getSoundId(*first);
|
|
subsounds.push_back(make_pair(CStringMapper::unmap(*first), sound));
|
|
}
|
|
}
|
|
|
|
|
|
uint32 CComplexSound::getDuration()
|
|
{
|
|
// evaluate the duration of the sound...
|
|
|
|
if (_DurationValid)
|
|
return _Duration;
|
|
|
|
// catch the duration of all sub sound.
|
|
CAudioMixerUser *mixer = CAudioMixerUser::instance();
|
|
|
|
vector<sint32> durations;
|
|
std::vector<NLMISC::TStringId>::iterator first(_Sounds.begin()), last(_Sounds.end());
|
|
for (; first != last; ++first)
|
|
{
|
|
CSound *sound = mixer->getSoundId(*first);
|
|
if (sound != NULL)
|
|
{
|
|
durations.push_back(sint32(sound->getDuration()));
|
|
}
|
|
else
|
|
durations.push_back(0);
|
|
|
|
}
|
|
|
|
_Duration = 0;
|
|
switch (_PatternMode)
|
|
{
|
|
case MODE_CHAINED:
|
|
{
|
|
// sum the duration minus the xfade time (this is an aproximation if sample are shorter than 2 xfade time)
|
|
vector<uint32>::iterator first(_SoundSeq.begin()), last(_SoundSeq.end()), prev;
|
|
for (; first != last; ++first)
|
|
{
|
|
if (first != _SoundSeq.begin() && !durations.empty())
|
|
{
|
|
// remove a xfade value
|
|
_Duration -= minof<uint32>(uint32(_XFadeLenght / _TicksPerSeconds), durations[*first % durations.size()] / 2, durations[*prev % durations.size()] /2);
|
|
}
|
|
if (!durations.empty())
|
|
_Duration += durations[*first % durations.size()];
|
|
prev = first;
|
|
}
|
|
// _Duration -= max(sint(0), sint(_XFadeLenght * (_SoundSeq.size()-2) ));
|
|
}
|
|
break;
|
|
case MODE_SPARSE:
|
|
{
|
|
if (_SoundSeq.empty())
|
|
_Duration = 0;
|
|
else if (_DelaySeq.empty())
|
|
{
|
|
_Duration = durations[0];
|
|
}
|
|
else if (_DelaySeq.size() == 1)
|
|
{
|
|
_Duration = durations[0] + _DelaySeq[0];
|
|
}
|
|
else
|
|
{
|
|
uint soundIndex = 0;
|
|
_Duration = 0; //durations[soundIndex++];
|
|
|
|
std::vector<uint32>::iterator first(_DelaySeq.begin()), last(_DelaySeq.end());
|
|
|
|
_Duration+= *first;
|
|
++first;
|
|
for (; first != last; ++first)
|
|
{
|
|
// add the sound lenght
|
|
_Duration += durations[soundIndex++ % durations.size()];
|
|
// add the delay
|
|
_Duration += uint32(*first / _TicksPerSeconds);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case MODE_ALL_IN_ONE:
|
|
// only find the longueur sound.
|
|
if (!durations.empty())
|
|
_Duration = *(std::max_element(durations.begin(), durations.end()));
|
|
else
|
|
_Duration = 0;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
_DurationValid = true;
|
|
return _Duration;
|
|
|
|
}
|
|
|
|
|
|
// ********************************************************
|
|
|
|
CComplexSound::CComplexSound() :
|
|
_PatternMode(CComplexSound::MODE_UNDEFINED),
|
|
_TicksPerSeconds(1.0f),
|
|
_XFadeLenght(3000), // default to 3000 sec.
|
|
_MaxDistValid(false),
|
|
_Duration(0),
|
|
_DurationValid(false)
|
|
{
|
|
}
|
|
|
|
// ********************************************************
|
|
|
|
CComplexSound::~CComplexSound()
|
|
{
|
|
/* if (_VolumeEnvelope != 0)
|
|
{
|
|
delete _VolumeEnvelope;
|
|
}
|
|
|
|
if (_FreqModulation != 0)
|
|
{
|
|
delete _FreqModulation;
|
|
}
|
|
*/
|
|
}
|
|
|
|
float CComplexSound::getMaxDistance() const
|
|
{
|
|
if (!_MaxDistValid)
|
|
{
|
|
// compute the max distance by checking the max distance of all sounds.
|
|
CAudioMixerUser *mixer = CAudioMixerUser::instance();
|
|
|
|
// Hum, getMaxDistance is const, but we must compute the real max dist and update it !
|
|
CComplexSound *This = const_cast<CComplexSound*>(this);
|
|
|
|
This->_MaxDist = 0.0f;
|
|
std::vector<NLMISC::TStringId>::const_iterator first(_Sounds.begin()), last(_Sounds.end());
|
|
|
|
for (; first != last; ++first)
|
|
{
|
|
CSound *sound = mixer->getSoundId(*first);
|
|
if( sound != NULL)
|
|
{
|
|
This->_MaxDist = max(_MaxDist, sound->getMaxDistance());
|
|
}
|
|
}
|
|
// security check.
|
|
if (_MaxDist == 0.0f)
|
|
This->_MaxDist = 1000000.0f;
|
|
}
|
|
|
|
_MaxDistValid = true;
|
|
return _MaxDist;
|
|
}
|
|
|
|
void CComplexSound::serial(NLMISC::IStream &s)
|
|
{
|
|
CSound::serial(s);
|
|
s.serialEnum(_PatternMode);
|
|
if (s.isReading())
|
|
{
|
|
uint32 nb;
|
|
s.serial(nb);
|
|
|
|
for (uint i=0; i<nb; ++i)
|
|
{
|
|
std::string name;
|
|
s.serial(name);
|
|
_Sounds.push_back(CStringMapper::map(name));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint32 nb = (uint32)_Sounds.size();
|
|
s.serial(nb);
|
|
for (uint i=0; i<nb; ++i)
|
|
{
|
|
std::string name = CStringMapper::unmap(_Sounds[i]);
|
|
s.serial(name);
|
|
}
|
|
}
|
|
s.serial(_TicksPerSeconds);
|
|
s.serialCont(_SoundSeq);
|
|
s.serialCont(_DelaySeq);
|
|
s.serial(_XFadeLenght);
|
|
s.serial(_DoFadeIn);
|
|
s.serial(_DoFadeOut);
|
|
|
|
if (s.isReading())
|
|
_DurationValid = false;
|
|
}
|
|
|
|
|
|
/// Load the sound parameters from georges' form
|
|
void CComplexSound::importForm(const std::string& filename, NLGEORGES::UFormElm& formRoot)
|
|
{
|
|
NLGEORGES::UFormElm *psoundType;
|
|
std::string dfnName;
|
|
|
|
_DurationValid = false;
|
|
|
|
// some basic checking.
|
|
formRoot.getNodeByName(&psoundType, ".SoundType");
|
|
nlassert(psoundType != NULL);
|
|
psoundType->getDfnName(dfnName);
|
|
nlassert(dfnName == "complex_sound.dfn");
|
|
|
|
// Call the base class
|
|
CSound::importForm(filename, formRoot);
|
|
|
|
// Beat per second.
|
|
formRoot.getValueByName(_TicksPerSeconds, ".SoundType.Beat");
|
|
//beat can't be null or negative!
|
|
if (_TicksPerSeconds <= 0.0f)
|
|
_TicksPerSeconds = 1.0f;
|
|
|
|
|
|
|
|
// List of sound int this pattern
|
|
NLGEORGES::UFormElm *psoundsArray;
|
|
_Sounds.clear();
|
|
formRoot.getNodeByName(&psoundsArray, ".SoundType.SoundList");
|
|
|
|
if (psoundsArray != NULL)
|
|
{
|
|
uint size;
|
|
psoundsArray->getArraySize(size);
|
|
for (uint i=0; i<size; ++i)
|
|
{
|
|
string soundname;
|
|
if (psoundsArray->getArrayValue(soundname, i))
|
|
{
|
|
soundname = CFile::getFilenameWithoutExtension(soundname);
|
|
_Sounds.push_back(CStringMapper::map(soundname));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Mode of the complex sound.
|
|
string mode;
|
|
formRoot.getValueByName(mode, ".SoundType.Mode");
|
|
|
|
if (mode == "Chained" || mode == "Sparse")
|
|
{
|
|
// XFade lenght
|
|
formRoot.getValueByName(_XFadeLenght, ".SoundType.XFadeLenght");
|
|
// Fade in/out flag.
|
|
formRoot.getValueByName(_DoFadeIn, ".SoundType.DoFadeIn");
|
|
formRoot.getValueByName(_DoFadeOut, ".SoundType.DoFadeOut");
|
|
|
|
// convert xfade to millisec.
|
|
_XFadeLenght *= 1000;
|
|
_PatternMode = MODE_CHAINED;
|
|
// just read the sequence
|
|
_SoundSeq.clear();
|
|
|
|
string str;
|
|
formRoot.getValueByName(str, ".SoundType.SoundSeq");
|
|
parseSequence(str, _SoundSeq);
|
|
|
|
if (mode == "Sparse")
|
|
{
|
|
_PatternMode = MODE_SPARSE;
|
|
// also read the delay sequence
|
|
_DelaySeq.clear();
|
|
|
|
string str;
|
|
formRoot.getValueByName(str, ".SoundType.DelaySeq");
|
|
// parse the delay and premult by 1000 (for millisec).
|
|
parseSequence(str, _DelaySeq, 1000);
|
|
}
|
|
}
|
|
else if (mode == "AllInOne")
|
|
{
|
|
_PatternMode = MODE_ALL_IN_ONE;
|
|
// nothing special to read.
|
|
}
|
|
else
|
|
nlassertex(false, ("Unsupported mode : %s", mode.c_str()));
|
|
|
|
}
|
|
|
|
}
|