1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2008-2014 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
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.
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"
20 #include "listener_xaudio2.h"
21 #include "source_xaudio2.h"
22 #include "effect_xaudio2.h"
23 #include "sound_driver_xaudio2.h"
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
41 // ******************************************************************
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
;
56 #endif /* #ifndef NL_STATIC */
58 // ***************************************************************************
66 // ***************************************************************************
69 ISoundDriver
* createISoundDriverInstanceXAudio2
71 __declspec(dllexport
) ISoundDriver
*NLSOUND_createISoundDriverInstance
73 (ISoundDriver::IStringMapperProvider
*stringMapper
)
75 return new CSoundDriverXAudio2(stringMapper
);
78 // ******************************************************************
81 uint32
interfaceVersionXAudio2()
83 __declspec(dllexport
) uint32
NLSOUND_interfaceVersion()
86 return ISoundDriver::InterfaceVersion
;
89 // ******************************************************************
92 void outputProfileXAudio2
94 __declspec(dllexport
) void NLSOUND_outputProfile
98 CSoundDriverXAudio2::getInstance()->writeProfile(out
);
101 // ******************************************************************
104 ISoundDriver::TDriver
getDriverTypeXAudio2()
106 __declspec(dllexport
) ISoundDriver::TDriver
NLSOUND_getDriverType()
109 return ISoundDriver::DriverXAudio2
;
112 // ******************************************************************
120 // ******************************************************************
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
);
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
);
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
;
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; }
186 // flags |= XAUDIO2_DEBUG_ENGINE; // comment when done using this :)
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()
200 if (_CoInitOk
) CoUninitialize();
203 nlassert(!_CoInitOk
);
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();
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();
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();
245 // Release internal resources of the IListener instance
246 if (_Listener
) { nlwarning(NLSOUND_XAUDIO2_PREFIX
"_Listener: !NULL"); _Listener
->release(); _Listener
= NULL
; }
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
260 _XAudio2
->GetDeviceCount(&deviceCount
);
262 if (deviceCount
== 0)
264 nldebug("XA2: No audio devices");
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
)
285 _XAudio2
->GetDeviceDetails(0, deviceDetails
);
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
)
300 nldebug("XA2: Device '%s' not found", device
.c_str());
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
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
=
322 | OptionManualRolloff
323 | OptionLocalBufferCopy
;
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; }
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; }
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()
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
)
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
; }
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
);
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 ...
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 ...
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
)
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 */