Fix "no remove aqua speed" bug when player leaves the water
[ryzomcore.git] / nel / src / sound / driver / xaudio2 / sound_driver_xaudio2.cpp
blobea08835a41847e277664b1b6754a5e39a101b3af
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2008-2014 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
17 #include "stdxaudio2.h"
19 // Project includes
20 #include "listener_xaudio2.h"
21 #include "source_xaudio2.h"
22 #include "effect_xaudio2.h"
23 #include "sound_driver_xaudio2.h"
25 #ifdef DEBUG_NEW
26 #define new DEBUG_NEW
27 #endif
29 using namespace std;
30 using namespace NLMISC;
32 /// Sample rate for master voice (device).
33 /// Default (0) under windows is 44100 (CD) or 48000 (DVD).
34 /// Note 1: OpenAL driver uses 22050 at the moment.
35 /// Note 2: 44100 seems to be the optimal value here.
36 //#define NLSOUND_XAUDIO2_MASTER_SAMPLE_RATE 44100
37 #define NLSOUND_XAUDIO2_MASTER_SAMPLE_RATE XAUDIO2_DEFAULT_SAMPLERATE
39 namespace NLSOUND {
41 // ******************************************************************
43 #ifndef NL_STATIC
44 class CSoundDriverXAudio2NelLibrary : public NLMISC::INelLibrary {
45 void onLibraryLoaded(bool /* firstTime */) { }
46 void onLibraryUnloaded(bool /* lastTime */) { }
48 NLMISC_DECL_PURE_LIB(CSoundDriverXAudio2NelLibrary)
50 HINSTANCE CSoundDriverXAudio2DllHandle = NULL;
51 BOOL WINAPI DllMain(HANDLE hModule, DWORD /* ul_reason_for_call */, LPVOID /* lpReserved */)
53 CSoundDriverXAudio2DllHandle = (HINSTANCE)hModule;
54 return TRUE;
56 #endif /* #ifndef NL_STATIC */
58 // ***************************************************************************
60 #ifndef NL_STATIC
61 #ifdef NL_COMP_MINGW
62 extern "C" {
63 #endif
64 #endif
66 // ***************************************************************************
68 #ifdef NL_STATIC
69 ISoundDriver* createISoundDriverInstanceXAudio2
70 #else
71 __declspec(dllexport) ISoundDriver *NLSOUND_createISoundDriverInstance
72 #endif
73 (ISoundDriver::IStringMapperProvider *stringMapper)
75 return new CSoundDriverXAudio2(stringMapper);
78 // ******************************************************************
80 #ifdef NL_STATIC
81 uint32 interfaceVersionXAudio2()
82 #else
83 __declspec(dllexport) uint32 NLSOUND_interfaceVersion()
84 #endif
86 return ISoundDriver::InterfaceVersion;
89 // ******************************************************************
91 #ifdef NL_STATIC
92 void outputProfileXAudio2
93 #else
94 __declspec(dllexport) void NLSOUND_outputProfile
95 #endif
96 (string &out)
98 CSoundDriverXAudio2::getInstance()->writeProfile(out);
101 // ******************************************************************
103 #ifdef NL_STATIC
104 ISoundDriver::TDriver getDriverTypeXAudio2()
105 #else
106 __declspec(dllexport) ISoundDriver::TDriver NLSOUND_getDriverType()
107 #endif
109 return ISoundDriver::DriverXAudio2;
112 // ******************************************************************
114 #ifndef NL_STATIC
115 #ifdef NL_COMP_MINGW
117 #endif
118 #endif
120 // ******************************************************************
122 #ifdef NL_DEBUG
124 static XAUDIO2_DEBUG_CONFIGURATION NLSOUND_XAUDIO2_DEBUG_CONFIGURATION_DISABLED = {
125 0, 0, true, true, true, true
128 NLMISC_CATEGORISED_COMMAND(nel, xa2DebugDisable, "", "")
130 human; quiet; log; args; rawCommandString;
131 CSoundDriverXAudio2::getInstance()->getXAudio2()->SetDebugConfiguration(&NLSOUND_XAUDIO2_DEBUG_CONFIGURATION_DISABLED);
132 return true;
135 static XAUDIO2_DEBUG_CONFIGURATION NLSOUND_XAUDIO2_DEBUG_CONFIGURATION_HEAVY = {
136 (UINT32)(~XAUDIO2_LOG_FUNC_CALLS & ~XAUDIO2_LOG_LOCKS & ~XAUDIO2_LOG_MEMORY), 0, true, true, true, true
139 NLMISC_CATEGORISED_COMMAND(nel, xa2DebugHeavy, "", "")
141 human; quiet; log; args; rawCommandString;
142 CSoundDriverXAudio2::getInstance()->getXAudio2()->SetDebugConfiguration(&NLSOUND_XAUDIO2_DEBUG_CONFIGURATION_HEAVY);
143 return true;
146 #endif /* NL_DEBUG */
148 // ******************************************************************
150 CSoundDriverXAudio2::CSoundDriverXAudio2(ISoundDriver::IStringMapperProvider * /* stringMapper */)
151 : _XAudio2(NULL), _MasteringVoice(NULL), _Listener(NULL),
152 _SoundDriverOk(false), _CoInitOk(false), _OperationSetCounter(65536),
153 _PerformanceCommit3DCounter(0), _PerformanceADPCMBufferSize(0),
154 _PerformancePCMBufferSize(0), _PerformanceSourcePlayCounter(0)
156 nlwarning(NLSOUND_XAUDIO2_PREFIX "Creating CSoundDriverXAudio2");
158 memset(&_X3DAudioHandle, 0, sizeof(_X3DAudioHandle));
159 memset(&_EmptyListener, 0, sizeof(_EmptyListener));
161 _EmptyListener.OrientFront.x = 0.0f;
162 _EmptyListener.OrientFront.y = 0.0f;
163 _EmptyListener.OrientFront.z = 1.0f;
164 _EmptyListener.OrientTop.x = 0.0f;
165 _EmptyListener.OrientTop.y = 1.0f;
166 _EmptyListener.OrientTop.z = 0.0f;
167 _EmptyListener.Position.x = 0.0f;
168 _EmptyListener.Position.y = 0.0f;
169 _EmptyListener.Position.z = 0.0f;
170 _EmptyListener.Velocity.x = 0.0f;
171 _EmptyListener.Velocity.y = 0.0f;
172 _EmptyListener.Velocity.z = 0.0f;
174 HRESULT hr;
176 // Windows
177 #ifdef NL_OS_WINDOWS // CoInitializeEx not on xbox, lol
178 hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
179 if (hr == RPC_E_CHANGED_MODE) { nlwarning(NLSOUND_XAUDIO2_PREFIX "CoInitializeEx COINIT_APARTMENTTHREADED"); hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); }
180 _CoInitOk = (hr == S_OK) || (hr == S_FALSE);
181 if (!_CoInitOk) { release(); throw ESoundDriver(NLSOUND_XAUDIO2_PREFIX "FAILED CoInitializeEx"); return; }
182 #endif
184 uint32 flags = 0;
185 #ifdef NL_DEBUG
186 // flags |= XAUDIO2_DEBUG_ENGINE; // comment when done using this :)
187 #endif
189 // XAudio2
190 if (FAILED(hr = XAudio2Create(&_XAudio2, flags, XAUDIO2_DEFAULT_PROCESSOR)))
191 { release(); throw ESoundDriver(NLSOUND_XAUDIO2_PREFIX "XAudio2 failed to initialize. Please install the latest version of the DirectX End-User Runtimes."); return; }
194 CSoundDriverXAudio2::~CSoundDriverXAudio2()
196 release();
198 // Windows
199 #ifdef NL_OS_WINDOWS
200 if (_CoInitOk) CoUninitialize();
201 _CoInitOk = false;
202 #else
203 nlassert(!_CoInitOk);
204 #endif
206 nlinfo(NLSOUND_XAUDIO2_PREFIX "Destroying CSoundDriverXAudio2");
209 #define NLSOUND_XAUDIO2_RELEASE(pointer) if (_SoundDriverOk) nlassert(pointer); \
210 /*if (pointer) {*/ delete pointer; pointer = NULL; /*}*/
211 #define NLSOUND_XAUDIO2_RELEASE_EX(pointer, command) if (_SoundDriverOk) nlassert(pointer); \
212 if (pointer) { command; pointer = NULL; }
214 void CSoundDriverXAudio2::release()
216 nlinfo(NLSOUND_XAUDIO2_PREFIX "Releasing CSoundDriverXAudio2");
218 // WARNING: Only internal resources are released here,
219 // the created instances must still be released by the user!
221 // Release internal resources of all remaining ISource instances
222 if (!_Sources.empty())
224 nlwarning(NLSOUND_XAUDIO2_PREFIX "_Sources.size(): '%u'", (uint32)_Sources.size());
225 set<CSourceXAudio2 *>::iterator it(_Sources.begin()), end(_Sources.end());
226 for (; it != end; ++it) (*it)->release();
227 _Sources.clear();
229 // Release internal resources of all remaining IBuffer instances
230 if (!_Buffers.empty())
232 nlwarning(NLSOUND_XAUDIO2_PREFIX "_Buffers.size(): '%u'", (uint32)_Buffers.size());
233 set<CBufferXAudio2 *>::iterator it(_Buffers.begin()), end(_Buffers.end());
234 for (; it != end; ++it) (*it)->release();
235 _Buffers.clear();
237 // Release internal resources of all remaining IEffect instances
238 if (!_Effects.empty())
240 nlwarning(NLSOUND_XAUDIO2_PREFIX "_Effects.size(): '%u'", (uint32)_Effects.size());
241 set<CEffectXAudio2 *>::iterator it(_Effects.begin()), end(_Effects.end());
242 for (; it != end; ++it) (*it)->release();
243 _Effects.clear();
245 // Release internal resources of the IListener instance
246 if (_Listener) { nlwarning(NLSOUND_XAUDIO2_PREFIX "_Listener: !NULL"); _Listener->release(); _Listener = NULL; }
248 // XAudio2
249 NLSOUND_XAUDIO2_RELEASE_EX(_MasteringVoice, _MasteringVoice->DestroyVoice());
250 NLSOUND_XAUDIO2_RELEASE_EX(_XAudio2, _XAudio2->Release());
251 _SoundDriverOk = false;
254 /// Return a list of available devices for the user. The value at index 0 is empty, and is used for automatic device selection.
255 void CSoundDriverXAudio2::getDevices(std::vector<std::string> &devices)
257 devices.push_back(""); // empty
259 UINT32 deviceCount;
260 _XAudio2->GetDeviceCount(&deviceCount);
262 if (deviceCount == 0)
264 nldebug("XA2: No audio devices");
266 else
268 XAUDIO2_DEVICE_DETAILS deviceDetails;
269 nldebug("XA2: Listing devices: ");
270 for (uint i = 0; i < deviceCount; ++i)
272 _XAudio2->GetDeviceDetails(i, &deviceDetails);
273 std::string deviceName = wideToUtf8(deviceDetails.DisplayName);
274 nldebug("XA2: - %s", deviceName.c_str());
275 devices.push_back(deviceName);
280 /// (Internal) Get device index and details from string.
281 uint CSoundDriverXAudio2::getDeviceIndex(const std::string &device, XAUDIO2_DEVICE_DETAILS *deviceDetails)
283 if (device.empty())
285 _XAudio2->GetDeviceDetails(0, deviceDetails);
286 return 0;
289 UINT32 deviceCount;
290 _XAudio2->GetDeviceCount(&deviceCount);
292 for (uint i = 0; i < deviceCount; ++i)
294 _XAudio2->GetDeviceDetails(i, deviceDetails);
295 std::string deviceName = wideToUtf8(deviceDetails->DisplayName);
296 if (deviceName == device)
297 return i;
300 nldebug("XA2: Device '%s' not found", device.c_str());
301 return 0;
304 /// Initialize the driver with a user selected device. If device.empty(), the default or most appropriate device is used.
305 void CSoundDriverXAudio2::initDevice(const std::string &device, TSoundOptions options)
307 nlinfo(NLSOUND_XAUDIO2_PREFIX "Initializing CSoundDriverXAudio2");
309 // list of supported options in this driver
310 const sint supportedOptions =
311 OptionEnvironmentEffects
312 | OptionAllowADPCM
313 | OptionSoftwareBuffer
314 | OptionManualRolloff
315 | OptionLocalBufferCopy
316 | OptionHasBufferStreaming;
318 // list of forced options in this driver
319 // always use software buffer, always have local copy
320 const sint forcedOptions =
321 OptionSoftwareBuffer
322 | OptionManualRolloff
323 | OptionLocalBufferCopy;
325 // set the options
326 _Options = (TSoundOptions)(((sint)options & supportedOptions) | forcedOptions);
328 #ifdef NL_OS_WINDOWS // CoInitializeEx not on xbox, lol
329 if (!_CoInitOk) { throw ESoundDriver(NLSOUND_XAUDIO2_PREFIX "FAILED CoInitializeEx"); return; }
330 #endif
332 HRESULT hr;
334 // XAudio2
335 XAUDIO2_DEVICE_DETAILS deviceDetails;
336 uint deviceIndex = getDeviceIndex(device, &deviceDetails);
337 if (FAILED(hr = _XAudio2->CreateMasteringVoice(&_MasteringVoice, 0, NLSOUND_XAUDIO2_MASTER_SAMPLE_RATE, 0, deviceIndex, NULL)))
338 { release(); throw ESoundDriver(NLSOUND_XAUDIO2_PREFIX "FAILED CreateMasteringVoice _MasteringVoice!"); return; }
340 // X3DAudio
341 // speed of sound in meters per second for dry air at approximately 20C, used with X3DAudioInitialize
342 // #define X3DAUDIO_SPEED_OF_SOUND 343.5f
343 X3DAudioInitialize(deviceDetails.OutputFormat.dwChannelMask, X3DAUDIO_SPEED_OF_SOUND, _X3DAudioHandle);
345 _SoundDriverOk = true;
348 /// Return options that are enabled (including those that cannot be disabled on this driver).
349 ISoundDriver::TSoundOptions CSoundDriverXAudio2::getOptions()
351 return _Options;
354 /// Return if an option is enabled (including those that cannot be disabled on this driver).
355 bool CSoundDriverXAudio2::getOption(ISoundDriver::TSoundOptions option)
357 return ((uint)_Options & (uint)option) == (uint)option;
360 /// Tell sources without voice about a format
361 void CSoundDriverXAudio2::initSourcesFormat(IBuffer::TBufferFormat bufferFormat, uint8 channels, uint8 bitsPerSample)
363 std::set<CSourceXAudio2 *>::iterator it(_Sources.begin()), end(_Sources.end());
364 for (; it != end; ++it) { if (!(*it)->getSourceVoice()) { (*it)->initFormat(bufferFormat, channels, bitsPerSample); } }
367 /// (Internal) Create an XAudio2 source voice of the specified format.
368 IXAudio2SourceVoice *CSoundDriverXAudio2::createSourceVoice(IBuffer::TBufferFormat bufferFormat, uint8 channels, uint8 bitsPerSample, IXAudio2VoiceCallback *callback)
370 nlassert(_Listener);
372 HRESULT hr;
374 WAVEFORMATEX wfe;
375 wfe.cbSize = 0;
377 if (bufferFormat == IBuffer::FormatDviAdpcm)
378 nlassert(channels == 1 && bitsPerSample == 16);
380 wfe.wFormatTag = WAVE_FORMAT_PCM; // DVI_ADPCM is converted in the driver
382 wfe.nChannels = channels;
383 wfe.wBitsPerSample = bitsPerSample;
385 XAUDIO2_VOICE_DETAILS voice_details;
386 _Listener->getDryVoice()->GetVoiceDetails(&voice_details);
387 wfe.nSamplesPerSec = voice_details.InputSampleRate;
389 wfe.nBlockAlign = wfe.nChannels * wfe.wBitsPerSample / 8;
390 wfe.nAvgBytesPerSec = wfe.nSamplesPerSec * wfe.nBlockAlign;
392 // TODO: Set callback (in CSourceXAudio2 maybe) for when error happens on voice, so we can restart it!
393 IXAudio2SourceVoice *source_voice = NULL;
395 if (FAILED(hr = _XAudio2->CreateSourceVoice(&source_voice, &wfe, 0, NLSOUND_MAX_PITCH, callback, NULL, NULL)))
396 { if (source_voice) source_voice->DestroyVoice(); nlerror(NLSOUND_XAUDIO2_PREFIX "FAILED CreateSourceVoice"); return NULL; }
398 return source_voice;
401 /// (Internal) Destroy an XAudio2 source voice.
402 void CSoundDriverXAudio2::destroySourceVoice(IXAudio2SourceVoice *sourceVoice)
404 if (sourceVoice) sourceVoice->DestroyVoice();
407 /// Create the listener instance
408 IListener *CSoundDriverXAudio2::createListener()
410 if (!_Listener) _Listener = new CListenerXAudio2(this);
411 return static_cast<IListener *>(_Listener);
414 /// Create a source, destroy with delete
415 ISource *CSoundDriverXAudio2::createSource()
417 CSourceXAudio2 *source = new CSourceXAudio2(this);
418 _Sources.insert(source);
419 return static_cast<ISource *>(source);
422 /// Create a sound buffer, destroy with delete
423 IBuffer *CSoundDriverXAudio2::createBuffer()
425 CBufferXAudio2 *buffer = new CBufferXAudio2(this);
426 _Buffers.insert(buffer);
427 return static_cast<IBuffer *>(buffer);
430 /// Create a reverb effect
431 IReverbEffect *CSoundDriverXAudio2::createReverbEffect()
433 CReverbEffectXAudio2 *reverb = new CReverbEffectXAudio2(this);
434 if (reverb->getEffect())
436 _Effects.insert(reverb);
437 return static_cast<IReverbEffect *>(reverb);
439 else
441 delete reverb;
442 return NULL;
446 /// Return the maximum number of sources that can created
447 uint CSoundDriverXAudio2::countMaxSources()
449 // the only limit is the user's cpu
450 // keep similar to openal limit for now ...
451 return 128;
454 /// Return the maximum number of effects that can be created
455 uint CSoundDriverXAudio2::countMaxEffects()
457 // the only limit is the user's cpu
458 // openal only allows 1 in software ...
459 return 32;
462 /// Commit all the changes made to 3D settings of listener and sources
463 void CSoundDriverXAudio2::commit3DChanges()
465 performanceIncreaseCommit3DCounter();
467 // Sync up sources & listener 3d position.
469 std::set<CSourceXAudio2 *>::iterator it(_Sources.begin()), end(_Sources.end());
470 for (; it != end; ++it) { (*it)->updateState(); (*it)->update3DChanges(); }
474 /// Write information about the driver to the output stream.
475 void CSoundDriverXAudio2::writeProfile(std::string& out)
477 XAUDIO2_PERFORMANCE_DATA performance;
478 _XAudio2->GetPerformanceData(&performance);
480 out = toString(NLSOUND_XAUDIO2_NAME)
481 + "\n\tPCMBufferSize: " + toString(_PerformancePCMBufferSize)
482 + "\n\tADPCMBufferSize: " + toString(_PerformanceADPCMBufferSize)
483 + "\n\tSourcePlayCounter: " + toString(_PerformanceSourcePlayCounter)
484 + "\n\tCommit3DCounter: " + toString(_PerformanceCommit3DCounter)
485 + "\nXAUDIO2_PERFORMANCE_DATA"
486 + "\n\tAudioCyclesSinceLastQuery: " + toString(performance.AudioCyclesSinceLastQuery)
487 + "\n\tTotalCyclesSinceLastQuery: " + toString(performance.TotalCyclesSinceLastQuery)
488 + "\n\tMinimumCyclesPerQuantum: " + toString(performance.MinimumCyclesPerQuantum)
489 + "\n\tMaximumCyclesPerQuantum: " + toString(performance.MaximumCyclesPerQuantum)
490 + "\n\tMemoryUsageInBytes: " + toString(performance.MemoryUsageInBytes)
491 + "\n\tCurrentLatencyInSamples: " + toString(performance.CurrentLatencyInSamples)
492 + "\n\tGlitchesSinceEngineStarted: " + toString(performance.GlitchesSinceEngineStarted)
493 + "\n\tActiveSourceVoiceCount: " + toString(performance.ActiveSourceVoiceCount)
494 + "\n\tTotalSourceVoiceCount: " + toString(performance.TotalSourceVoiceCount)
495 + "\n\tActiveSubmixVoiceCount: " + toString(performance.ActiveSubmixVoiceCount)
496 + "\n\tActiveXmaSourceVoices: " + toString(performance.ActiveXmaSourceVoices)
497 + "\n\tActiveXmaStreams: " + toString(performance.ActiveXmaStreams)
498 + "\n";
499 return;
502 // Does not create a sound loader .. what does it do then?
503 void CSoundDriverXAudio2::startBench()
505 NLMISC::CHTimer::startBench();
508 void CSoundDriverXAudio2::endBench()
510 NLMISC::CHTimer::endBench();
513 void CSoundDriverXAudio2::displayBench(NLMISC::CLog *log)
515 NLMISC::CHTimer::displayHierarchicalByExecutionPathSorted(log, CHTimer::TotalTime, true, 48, 2);
516 NLMISC::CHTimer::displayHierarchical(log, true, 48, 2);
517 NLMISC::CHTimer::displayByExecutionPath(log, CHTimer::TotalTime);
518 NLMISC::CHTimer::display(log, CHTimer::TotalTime);
521 /// Remove a buffer (should be called by the friend destructor of the buffer class)
522 void CSoundDriverXAudio2::removeBuffer(CBufferXAudio2 *buffer)
524 if (_Buffers.find(buffer) != _Buffers.end()) _Buffers.erase(buffer);
525 else nlwarning("removeBuffer already called");
528 /// Remove a source (should be called by the friend destructor of the source class)
529 void CSoundDriverXAudio2::removeSource(CSourceXAudio2 *source)
531 if (_Sources.find(source) != _Sources.end()) _Sources.erase(source);
532 else nlwarning("removeSource already called");
535 /// (Internal) Remove an effect (should be called by the destructor of the effect class)
536 void CSoundDriverXAudio2::removeEffect(CEffectXAudio2 *effect)
538 if (_Effects.find(effect) != _Effects.end()) _Effects.erase(effect);
539 else nlwarning("removeEffect already called");
542 } /* namespace NLSOUND */
544 /* end of file */