1 // NeL - MMORPG Framework <https://wiki.ryzom.dev/>
2 // Copyright (C) 2010-2012 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
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 Winch Gate Property Limited
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/>.
22 #include "nel/sound/stream_source.h"
25 #include "nel/sound/driver/buffer.h"
26 #include "nel/sound/audio_mixer_user.h"
27 #include "nel/sound/stream_sound.h"
28 #include "nel/sound/clustered_sound.h"
30 // using namespace std;
31 using namespace NLMISC
;
33 // #define NLSOUND_DEBUG_STREAM
37 CStreamSource::CStreamSource(CStreamSound
*streamSound
, bool spawn
, TSpawnEndCallback cb
, void *cbUserParam
, NL3D::CCluster
*cluster
, CGroupController
*groupController
)
38 : CSourceCommon(streamSound
, spawn
, cb
, cbUserParam
, cluster
, groupController
),
39 m_StreamSound(streamSound
),
46 m_WaitingForPlay(false),
49 nlassert(m_StreamSound
!= 0);
51 // get a local copy of the stream sound parameter
52 m_Alpha
= m_StreamSound
->getAlpha();//m_Buffers
53 m_PitchInv
= 1.0f
/ _Pitch
;
55 // create the three buffer objects
56 CAudioMixerUser
*mixer
= CAudioMixerUser::instance();
57 ISoundDriver
*driver
= mixer
->getSoundDriver();
58 m_Buffers
[0] = driver
->createBuffer();
59 m_Buffers
[0]->setStorageMode(IBuffer::StorageSoftware
);
60 m_Buffers
[1] = driver
->createBuffer();
61 m_Buffers
[1]->setStorageMode(IBuffer::StorageSoftware
);
62 m_Buffers
[2] = driver
->createBuffer();
63 m_Buffers
[2]->setStorageMode(IBuffer::StorageSoftware
);
66 CStreamSource::~CStreamSource()
71 if (m_Buffers
[0] != NULL
) { delete m_Buffers
[0]; m_Buffers
[0] = NULL
; }
72 if (m_Buffers
[1] != NULL
) { delete m_Buffers
[1]; m_Buffers
[1] = NULL
; }
73 if (m_Buffers
[2] != NULL
) { delete m_Buffers
[2]; m_Buffers
[2] = NULL
; }
76 void CStreamSource::initPhysicalSource()
78 CAudioMixerUser
*mixer
= CAudioMixerUser::instance();
79 CTrack
*track
= mixer
->getFreeTrack(this);
82 nlassert(track
->hasPhysicalSource());
84 getPhysicalSource()->setStreaming(true);
88 void CStreamSource::releasePhysicalSource()
90 if (hasPhysicalSource())
92 CAudioMixerUser
*mixer
= CAudioMixerUser::instance();
93 ISource
*pSource
= getPhysicalSource();
94 nlassert(pSource
!= NULL
);
98 pSource
->setStreaming(false);
99 if (mixer
) mixer
->freeTrack(m_Track
);
104 uint32
CStreamSource::getTime()
106 CAutoMutex
<CMutex
> autoMutex(m_BufferMutex
);
108 if (hasPhysicalSource())
109 return getPhysicalSource()->getTime();
114 bool CStreamSource::isPlaying()
119 /// Set looping on/off for future playbacks (default: off)
120 void CStreamSource::setLooping(bool l
)
122 CSourceCommon::setLooping(l
);
124 //CAutoMutex<CMutex> autoMutex(m_BufferMutex);
126 //CSourceCommon::setLooping(l);
127 //if (hasPhysicalSource())
128 // getPhysicalSource()->setLooping(l);
131 CVector
CStreamSource::getVirtualPos() const
133 if (getCluster() != 0)
135 // need to check the cluster status
136 const CClusteredSound::CClusterSoundStatus
*css
= CAudioMixerUser::instance()->getClusteredSound()->getClusterSoundStatus(getCluster());
139 // there is some data here, update the virtual position of the sound.
140 float dist
= (css
->Position
- getPos()).norm();
141 CVector
vpos(CAudioMixerUser::instance()->getListenPosVector() + css
->Direction
* (css
->Dist
+ dist
));
142 vpos
= _Position
* (1-css
->PosAlpha
) + vpos
*(css
->PosAlpha
);
150 void CStreamSource::play()
154 CAudioMixerUser
*mixer
= CAudioMixerUser::instance();
157 CAutoMutex
<CMutex
> autoMutex(m_BufferMutex
);
159 //if ((mixer->getListenPosVector() - _Position).sqrnorm() > m_StreamSound->getMaxDistance() * m_StreamSound->getMaxDistance())
160 if ((_RelativeMode
? getPos().sqrnorm() : (mixer
->getListenPosVector() - getPos()).sqrnorm()) > m_StreamSound
->getMaxDistance() * m_StreamSound
->getMaxDistance())
162 // Source is too far to play
163 m_WaitingForPlay
= false;
166 if (_SpawnEndCb
!= NULL
)
167 _SpawnEndCb(this, _CbUserParam
);
170 #ifdef NLSOUND_DEBUG_STREAM
171 nldebug("CStreamSource %p : play FAILED, source is too far away !", (CAudioMixerUser::IMixerEvent
*)this);
176 CAudioMixerUser
*mixer
= CAudioMixerUser::instance();
178 if (!hasPhysicalSource())
179 initPhysicalSource();
181 if (hasPhysicalSource())
183 ISource
*pSource
= getPhysicalSource();
184 nlassert(pSource
!= NULL
);
186 uint nbS
= m_NextBuffer
;
187 if (!m_NextBuffer
&& !m_FreeBuffers
) nbS
= 3;
188 for (uint i
= 0; i
< nbS
; ++i
)
189 pSource
->submitStreamingBuffer(m_Buffers
[i
]);
191 // pSource->setPos( _Position, false);
192 pSource
->setPos(getVirtualPos(), false);
193 pSource
->setMinMaxDistances(m_StreamSound
->getMinDistance(), m_StreamSound
->getMaxDistance(), false);
194 if (!m_Buffers
[0]->isStereo())
196 setDirection(_Direction
); // because there is a workaround inside
197 pSource
->setVelocity(_Velocity
);
201 pSource
->setDirection(NLMISC::CVector::I
);
202 pSource
->setCone(float(Pi
* 2), float(Pi
* 2), 1.0f
);
203 pSource
->setVelocity(NLMISC::CVector::Null
);
205 pSource
->setGain(getFinalGain());
206 pSource
->setSourceRelativeMode(_RelativeMode
);
207 // pSource->setLooping(_Looping);
208 pSource
->setPitch(_Pitch
);
209 pSource
->setAlpha(m_Alpha
);
211 // and play the sound
212 nlassert(nbS
); // must have buffered already!
213 play
= pSource
->play();
214 // nldebug("CStreamSource %p : REAL play done", (CAudioMixerUser::IMixerEvent*)this);
218 if (_Priority
== HighestPri
)
220 // This sound is not discardable, add it in waiting playlist
221 mixer
->addSourceWaitingForPlay(this);
222 m_WaitingForPlay
= true;
227 // No source available, kill.
228 m_WaitingForPlay
= false;
231 if (_SpawnEndCb
!= NULL
)
232 _SpawnEndCb(this, _CbUserParam
);
241 CSourceCommon::play();
242 m_WaitingForPlay
= false;
243 #ifdef NLSOUND_DEBUG_STREAM
245 nlwarning("--- DUMP SOURCE INFO ---");
246 nlwarning(" * getLooping: %s", getPhysicalSource()->getLooping() ? "YES" : "NO");
247 nlwarning(" * isPlaying: %s", getPhysicalSource()->isPlaying() ? "YES" : "NO");
248 nlwarning(" * isStopped: %s", getPhysicalSource()->isStopped() ? "YES" : "NO");
249 nlwarning(" * isPaused: %s", getPhysicalSource()->isPaused() ? "YES" : "NO");
250 nlwarning(" * getPos: %f, %f, %f", getPhysicalSource()->getPos().x
, getPhysicalSource()->getPos().y
, getPhysicalSource()->getPos().z
);
252 getPhysicalSource()->getVelocity(v
);
253 nlwarning(" * getVelocity: %f, %f, %f", v
.x
, v
.y
, v
.z
);
254 getPhysicalSource()->getDirection(v
);
255 nlwarning(" * getDirection: %f, %f, %f", v
.x
, v
.y
, v
.z
);
256 nlwarning(" * getGain: %f", getPhysicalSource()->getGain());
257 nlwarning(" * getPitch: %f", getPhysicalSource()->getPitch());
258 nlwarning(" * getSourceRelativeMode: %s", getPhysicalSource()->getSourceRelativeMode() ? "YES" : "NO");
260 getPhysicalSource()->getMinMaxDistances(a
, b
);
261 nlwarning(" * getMinMaxDistances: %f, %f", a
, b
);
262 getPhysicalSource()->getCone(a
, b
, c
);
263 nlwarning(" * getCone: %f, %f", a
, b
, c
);
264 nlwarning(" * getDirect: %s", getPhysicalSource()->getDirect() ? "YES" : "NO");
265 nlwarning(" * getDirectGain: %f", getPhysicalSource()->getDirectGain());
266 nlwarning(" * isDirectFilterEnabled: %s", getPhysicalSource()->isDirectFilterEnabled() ? "YES" : "NO");
267 nlwarning(" * getEffect: %s", getPhysicalSource()->getEffect() ? "YES" : "NO");
268 nlwarning(" * getEffectGain: %f", getPhysicalSource()->getEffectGain());
269 nlwarning(" * isEffectFilterEnabled: %s", getPhysicalSource()->isEffectFilterEnabled() ? "YES" : "NO");
278 nlwarning("Failed to play physical sound source. This is a serious error");
282 void CStreamSource::stopInt()
284 CAutoMutex
<CMutex
> autoMutex(m_BufferMutex
);
286 // nldebug("CStreamSource %p : stop", (CAudioMixerUser::IMixerEvent*)this);
287 // nlassert(_Playing);
289 if (m_WaitingForPlay
)
291 nlassert(!_Playing
); // cannot already be playing if waiting for play
292 CAudioMixerUser
*mixer
= CAudioMixerUser::instance();
293 mixer
->removeSourceWaitingForPlay(this);
298 m_WaitingForPlay
= false;
302 if (hasPhysicalSource())
303 releasePhysicalSource();
305 CSourceCommon::stop();
310 m_WaitingForPlay
= false;
314 void CStreamSource::stop()
320 if (_SpawnEndCb
!= NULL
)
321 _SpawnEndCb(this, _CbUserParam
);
326 void CStreamSource::setPos(const NLMISC::CVector
& pos
)
328 CAutoMutex
<CMutex
> autoMutex(m_BufferMutex
);
330 CSourceCommon::setPos(pos
);
331 if (hasPhysicalSource())
332 getPhysicalSource()->setPos(getVirtualPos());
335 void CStreamSource::setVelocity(const NLMISC::CVector
& vel
)
337 CAutoMutex
<CMutex
> autoMutex(m_BufferMutex
);
339 CSourceCommon::setVelocity(vel
);
340 if (hasPhysicalSource())
341 getPhysicalSource()->setVelocity(vel
);
345 * Set the direction vector (3D mode only, ignored in stereo mode) (default: (0,0,0) as non-directional)
347 void CStreamSource::setDirection(const NLMISC::CVector
& dir
)
349 CAutoMutex
<CMutex
> autoMutex(m_BufferMutex
);
351 CSourceCommon::setDirection(dir
);
354 if (hasPhysicalSource())
356 if (!m_Buffers
[0]->isStereo())
358 static bool coneset
= false;
359 if (dir
.isNull()) // workaround // For what?
361 getPhysicalSource()->setCone(float(Pi
* 2), float(Pi
* 2), 1.0f
); // because the direction with 0 is not enough for a non-directional source!
362 getPhysicalSource()->setDirection(CVector::I
); // Don't send a 0 vector, DSound will complain. Send (1,0,0), it's omnidirectional anyway.
369 getPhysicalSource()->setCone(m_StreamSound
->getConeInnerAngle(), m_StreamSound
->getConeOuterAngle(), m_StreamSound
->getConeOuterGain());
372 getPhysicalSource()->setDirection(dir
);
378 void CStreamSource::updateFinalGain()
380 CAutoMutex
<CMutex
> autoMutex(m_BufferMutex
);
382 if (hasPhysicalSource())
383 getPhysicalSource()->setGain(getFinalGain());
386 void CStreamSource::setPitch(float pitch
)
388 CAutoMutex
<CMutex
> autoMutex(m_BufferMutex
);
389 m_PitchInv
= 1.0f
/ pitch
;
390 CSourceCommon::setPitch(pitch
);
391 if (hasPhysicalSource())
392 getPhysicalSource()->setPitch(pitch
);
395 void CStreamSource::setSourceRelativeMode(bool mode
)
397 CAutoMutex
<CMutex
> autoMutex(m_BufferMutex
);
399 CSourceCommon::setSourceRelativeMode(mode
);
400 if (hasPhysicalSource())
401 getPhysicalSource()->setSourceRelativeMode(mode
);
404 /// Set the sample format. (channels = 1, 2, ...; bitsPerSample = 8, 16; frequency = samples per second, 44100, ...)
405 void CStreamSource::setFormat(uint8 channels
, uint8 bitsPerSample
, uint32 frequency
)
409 m_Buffers
[0]->setFormat(IBuffer::FormatPcm
, channels
, bitsPerSample
, frequency
);
410 m_Buffers
[1]->setFormat(IBuffer::FormatPcm
, channels
, bitsPerSample
, frequency
);
411 m_Buffers
[2]->setFormat(IBuffer::FormatPcm
, channels
, bitsPerSample
, frequency
);
413 m_BytesPerSecond
= ((uint
)bitsPerSample
* (uint
)frequency
* (uint
)channels
) / 8;
416 /// Return the sample format information.
417 void CStreamSource::getFormat(uint8
&channels
, uint8
&bitsPerSample
, uint32
&frequency
) const
419 IBuffer::TBufferFormat bufferFormat
;
421 m_Buffers
[0]->getFormat(bufferFormat
, channels
, bitsPerSample
, frequency
);
424 void CStreamSource::updateAvailableBuffers()
426 if (hasPhysicalSource())
428 m_FreeBuffers
= 3 - getPhysicalSource()->countStreamingBuffers();
432 /// Get a writable pointer to the buffer of specified size. Use capacity to specify the required bytes. Returns NULL when all the buffer space is already filled. Call setFormat() first.
433 uint8
*CStreamSource::lock(uint capacity
)
435 CAutoMutex
<CMutex
> autoMutex(m_BufferMutex
);
436 updateAvailableBuffers();
437 if (m_FreeBuffers
> 0)
438 return m_Buffers
[m_NextBuffer
]->lock(capacity
);
442 /// Notify that you are done writing to the locked buffer, so it can be copied over to hardware if needed. Set size to the number of bytes actually written to the buffer. Returns true if ok.
443 bool CStreamSource::unlock(uint size
)
445 nlassert(m_FreeBuffers
> 0);
447 CAutoMutex
<CMutex
> autoMutex(m_BufferMutex
);
448 IBuffer
*buffer
= m_Buffers
[m_NextBuffer
];
449 bool result
= buffer
->unlock(size
);
453 ++m_NextBuffer
; m_NextBuffer
%= 3;
455 if (hasPhysicalSource())
457 getPhysicalSource()->submitStreamingBuffer(buffer
);
465 /// Get the recommended buffer size to use with lock()/unlock()
466 void CStreamSource::getRecommendedBufferSize(uint
&samples
, uint
&bytes
) const
468 IBuffer::TBufferFormat bufferFormat
;
472 m_Buffers
[0]->getFormat(bufferFormat
, channels
, bitsPerSample
, frequency
);
474 samples
= frequency
/ 25; // 25 is a good value
475 bytes
= ((uint
)bitsPerSample
* samples
* (uint
)channels
) / 8;
478 /// Get the recommended sleep time based on the size of the last submitted buffer and the available buffer space
479 uint32
CStreamSource::getRecommendedSleepTime() const
481 if (m_FreeBuffers
> 0) return 0;
482 uint32 sleepTime
= (uint32
)((1000.0f
* ((float)m_LastSize
) / (float)m_BytesPerSecond
) * m_PitchInv
);
483 clamp(sleepTime
, (uint32
)0, (uint32
)80);
487 /// Return if there are still buffers available for playback.
488 bool CStreamSource::hasFilledBuffersAvailable() const
490 const_cast<CStreamSource
*>(this)->updateAvailableBuffers();
491 return m_FreeBuffers
< 3;
494 void CStreamSource::preAllocate(uint capacity
)
496 uint8
*b0
= m_Buffers
[0]->lock(capacity
);
497 memset(b0
, 0, capacity
);
498 m_Buffers
[0]->unlock(capacity
);
499 uint8
*b1
= m_Buffers
[1]->lock(capacity
);
500 memset(b1
, 0, capacity
);
501 m_Buffers
[1]->unlock(capacity
);
502 uint8
*b2
= m_Buffers
[2]->lock(capacity
);
503 memset(b2
, 0, capacity
);
504 m_Buffers
[2]->unlock(capacity
);
507 } /* namespace NLSOUND */