Merge branch 'ryzom/ark-features' into main/gingo-test
[ryzomcore.git] / nel / src / sound / complex_sound.cpp
blobb1b96df0114ddefa014665d0ab8310b2c1f4ed88
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2010 Matt RAYKOWSKI (sfb) <matt.raykowski@gmail.com>
6 // Copyright (C) 2019 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
7 //
8 // This program is free software: you can redistribute it and/or modify
9 // it under the terms of the GNU Affero General Public License as
10 // published by the Free Software Foundation, either version 3 of the
11 // License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU Affero General Public License for more details.
18 // You should have received a copy of the GNU Affero General Public License
19 // along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "stdsound.h"
22 #include "nel/sound/complex_sound.h"
23 #include "nel/misc/path.h"
24 #include "nel/misc/common.h"
25 #include "nel/sound/audio_mixer_user.h"
27 using namespace std;
28 using namespace NLMISC;
30 namespace NLSOUND
33 bool CComplexSound::isDetailed() const
35 return false;
38 void CComplexSound::parseSequence(const std::string &str, std::vector<uint32> &seq, uint scale)
40 seq.clear();
42 uint32 tmp;
43 string val;
44 string::const_iterator first(str.begin()), last(str.end());
46 for (; first != last; ++first)
48 if (*first != ';')
49 val += *first;
50 else
52 fromString(val, tmp);
53 seq.push_back(tmp * scale);
54 val.clear();
58 // parse the last value
59 if (!val.empty())
61 fromString(val, tmp);
62 seq.push_back(tmp * scale);
67 void CComplexSound::getSubSoundList(std::vector<std::pair<std::string, CSound*> > &subsounds) const
69 CAudioMixerUser *mixer = CAudioMixerUser::instance();
70 std::vector<NLMISC::TStringId>::const_iterator first(_Sounds.begin()), last(_Sounds.end());
71 for (; first != last; ++first)
73 CSound *sound = mixer->getSoundId(*first);
74 subsounds.push_back(make_pair(CStringMapper::unmap(*first), sound));
79 uint32 CComplexSound::getDuration()
81 // evaluate the duration of the sound...
83 if (_DurationValid)
84 return _Duration;
86 // catch the duration of all sub sound.
87 CAudioMixerUser *mixer = CAudioMixerUser::instance();
89 vector<sint32> durations;
90 std::vector<NLMISC::TStringId>::iterator first(_Sounds.begin()), last(_Sounds.end());
91 for (; first != last; ++first)
93 CSound *sound = mixer->getSoundId(*first);
94 if (sound != NULL)
96 durations.push_back(sint32(sound->getDuration()));
98 else
99 durations.push_back(0);
103 _Duration = 0;
104 switch (_PatternMode)
106 case MODE_CHAINED:
108 // sum the duration minus the xfade time (this is an aproximation if sample are shorter than 2 xfade time)
109 vector<uint32>::iterator first(_SoundSeq.begin()), last(_SoundSeq.end()), prev;
110 for (; first != last; ++first)
112 if (first != _SoundSeq.begin() && !durations.empty())
114 // remove a xfade value
115 _Duration -= minof<uint32>(uint32(_XFadeLength / _TicksPerSeconds), durations[*first % durations.size()] / 2, durations[*prev % durations.size()] /2);
117 if (!durations.empty())
118 _Duration += durations[*first % durations.size()];
119 prev = first;
121 // _Duration -= max(sint(0), sint(_XFadeLength * (_SoundSeq.size()-2) ));
123 break;
124 case MODE_SPARSE:
126 if (_SoundSeq.empty())
127 _Duration = 0;
128 else if (_DelaySeq.empty())
130 _Duration = durations[0];
132 else if (_DelaySeq.size() == 1)
134 _Duration = durations[0] + _DelaySeq[0];
136 else
138 uint soundIndex = 0;
139 _Duration = 0; //durations[soundIndex++];
141 std::vector<uint32>::iterator first(_DelaySeq.begin()), last(_DelaySeq.end());
143 _Duration+= *first;
144 ++first;
145 for (; first != last; ++first)
147 // add the sound length
148 _Duration += durations[soundIndex++ % durations.size()];
149 // add the delay
150 _Duration += uint32(*first / _TicksPerSeconds);
154 break;
155 case MODE_ALL_IN_ONE:
156 // only find the longueur sound.
157 if (!durations.empty())
158 _Duration = *(std::max_element(durations.begin(), durations.end()));
159 else
160 _Duration = 0;
161 break;
162 default:
163 return 0;
166 _DurationValid = true;
167 return _Duration;
172 // ********************************************************
174 CComplexSound::CComplexSound() :
175 _PatternMode(CComplexSound::MODE_UNDEFINED),
176 _TicksPerSeconds(1.0f),
177 _XFadeLength(3000), // default to 3000 sec.
178 _DoFadeIn(true),
179 _DoFadeOut(true),
180 _MaxDistValid(false),
181 _Duration(0),
182 _DurationValid(false)
186 // ********************************************************
188 CComplexSound::~CComplexSound()
190 /* if (_VolumeEnvelope != 0)
192 delete _VolumeEnvelope;
195 if (_FreqModulation != 0)
197 delete _FreqModulation;
202 float CComplexSound::getMaxDistance() const
204 if (!_MaxDistValid)
206 // compute the max distance by checking the max distance of all sounds.
207 CAudioMixerUser *mixer = CAudioMixerUser::instance();
209 // Hum, getMaxDistance is const, but we must compute the real max dist and update it !
210 CComplexSound *This = const_cast<CComplexSound*>(this);
212 This->_MaxDist = 0.0f;
213 std::vector<NLMISC::TStringId>::const_iterator first(_Sounds.begin()), last(_Sounds.end());
215 for (; first != last; ++first)
217 CSound *sound = mixer->getSoundId(*first);
218 if( sound != NULL)
220 This->_MaxDist = max(_MaxDist, sound->getMaxDistance());
223 // security check.
224 if (_MaxDist == 0.0f)
225 This->_MaxDist = 1000000.0f;
228 _MaxDistValid = true;
229 return _MaxDist;
232 void CComplexSound::serial(NLMISC::IStream &s)
234 CSound::serial(s);
235 s.serialEnum(_PatternMode);
236 if (s.isReading())
238 uint32 nb;
239 s.serial(nb);
241 for (uint i=0; i<nb; ++i)
243 std::string name;
244 s.serial(name);
245 _Sounds.push_back(CStringMapper::map(name));
248 else
250 uint32 nb = (uint32)_Sounds.size();
251 s.serial(nb);
252 for (uint i=0; i<nb; ++i)
254 std::string name = CStringMapper::unmap(_Sounds[i]);
255 s.serial(name);
258 s.serial(_TicksPerSeconds);
259 s.serialCont(_SoundSeq);
260 s.serialCont(_DelaySeq);
261 s.serial(_XFadeLength);
262 s.serial(_DoFadeIn);
263 s.serial(_DoFadeOut);
265 if (s.isReading())
266 _DurationValid = false;
270 /// Load the sound parameters from georges' form
271 void CComplexSound::importForm(const std::string& filename, NLGEORGES::UFormElm& formRoot)
273 NLGEORGES::UFormElm *psoundType;
274 std::string dfnName;
276 _DurationValid = false;
278 // some basic checking.
279 formRoot.getNodeByName(&psoundType, ".SoundType");
280 nlassert(psoundType != NULL);
281 psoundType->getDfnName(dfnName);
282 nlassert(dfnName == "complex_sound.dfn");
284 // Call the base class
285 CSound::importForm(filename, formRoot);
287 // Beat per second.
288 formRoot.getValueByName(_TicksPerSeconds, ".SoundType.Beat");
289 //beat can't be null or negative!
290 if (_TicksPerSeconds <= 0.0f)
291 _TicksPerSeconds = 1.0f;
295 // List of sound int this pattern
296 NLGEORGES::UFormElm *psoundsArray;
297 _Sounds.clear();
298 formRoot.getNodeByName(&psoundsArray, ".SoundType.SoundList");
300 if (psoundsArray != NULL)
302 uint size;
303 psoundsArray->getArraySize(size);
304 for (uint i=0; i<size; ++i)
306 string soundname;
307 if (psoundsArray->getArrayValue(soundname, i))
309 soundname = CFile::getFilenameWithoutExtension(soundname);
310 _Sounds.push_back(CStringMapper::map(soundname));
316 // Mode of the complex sound.
317 string mode;
318 formRoot.getValueByName(mode, ".SoundType.Mode");
320 if (mode == "Chained" || mode == "Sparse")
322 // XFade length
323 if (!formRoot.getValueByName(_XFadeLength, ".SoundType.XFadeLength"))
324 formRoot.getValueByName(_XFadeLength, ".SoundType.XFadeLenght"); // WORKAROUND: Typo in sound assets
325 // Fade in/out flag.
326 formRoot.getValueByName(_DoFadeIn, ".SoundType.DoFadeIn");
327 formRoot.getValueByName(_DoFadeOut, ".SoundType.DoFadeOut");
329 // convert xfade to millisec.
330 _XFadeLength *= 1000;
331 _PatternMode = MODE_CHAINED;
332 // just read the sequence
333 _SoundSeq.clear();
335 string str;
336 formRoot.getValueByName(str, ".SoundType.SoundSeq");
337 parseSequence(str, _SoundSeq);
339 if (mode == "Sparse")
341 _PatternMode = MODE_SPARSE;
342 // also read the delay sequence
343 _DelaySeq.clear();
345 string str;
346 formRoot.getValueByName(str, ".SoundType.DelaySeq");
347 // parse the delay and premult by 1000 (for millisec).
348 parseSequence(str, _DelaySeq, 1000);
351 else if (mode == "AllInOne")
353 _PatternMode = MODE_ALL_IN_ONE;
354 // nothing special to read.
356 else
357 nlassertex(false, ("Unsupported mode : %s", mode.c_str()));