diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index a3628e2..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "DirectXTK12"]
- path = DirectXTK12
- url = git@github.com:microsoft/DirectXTK12.git
diff --git a/Common/DeviceResources.cpp b/Common/DeviceResources.cpp
index c0b7f10..45ed2c0 100644
--- a/Common/DeviceResources.cpp
+++ b/Common/DeviceResources.cpp
@@ -76,13 +76,14 @@ DeviceResources::DeviceResources(
// Destructor for DeviceResources.
DeviceResources::~DeviceResources()
{
- ThrowIfFailed(m_swapChain->SetFullscreenState(FALSE, NULL));
- // Ensure that the GPU is no longer referencing resources that are about to be destroyed.
+ // Ensure that the GPU is no longer referencing resources that are about to be destroyed.
WaitForGpu();
+
+ if (m_fullScreenMode) ThrowIfFailed(m_swapChain->SetFullscreenState(FALSE, nullptr));
}
// Configures the Direct3D device, and stores handles to it and the device context.
-void DeviceResources::CreateDeviceResources()
+void DeviceResources::CreateDeviceResources(BOOL fullScreenMode)
{
#if defined(_DEBUG)
// Enable the debug layer (requires the Graphics Tools "optional feature").
@@ -123,6 +124,10 @@ void DeviceResources::CreateDeviceResources()
ThrowIfFailed(CreateDXGIFactory2(m_dxgiFactoryFlags, IID_PPV_ARGS(m_dxgiFactory.ReleaseAndGetAddressOf())));
+ m_fullScreenMode = fullScreenMode;
+ if (m_fullScreenMode)
+ m_options = 0x0;
+
// Determines whether tearing support is available for fullscreen borderless windows.
if (m_options & c_AllowTearing)
{
@@ -337,7 +342,7 @@ void DeviceResources::CreateWindowSizeDependentResources()
swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
- swapChainDesc.Flags = m_options;
+ swapChainDesc.Flags = (m_options & c_AllowTearing) ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0u;
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fsSwapChainDesc = {};
fsSwapChainDesc.Windowed = TRUE;
@@ -357,15 +362,18 @@ void DeviceResources::CreateWindowSizeDependentResources()
// This class does not support exclusive full-screen mode and prevents DXGI from responding to the ALT+ENTER shortcut
ThrowIfFailed(m_dxgiFactory->MakeWindowAssociation(m_window, DXGI_MWA_NO_ALT_ENTER));
- ThrowIfFailed(m_swapChain->SetFullscreenState(TRUE, NULL));
- ThrowIfFailed(m_swapChain->ResizeBuffers(
- m_backBufferCount,
- backBufferWidth,
- backBufferHeight,
- backBufferFormat,
- m_options
- ));
+ if (m_fullScreenMode)
+ {
+ ThrowIfFailed(m_swapChain->SetFullscreenState(TRUE, NULL));
+ ThrowIfFailed(m_swapChain->ResizeBuffers(
+ m_backBufferCount,
+ backBufferWidth,
+ backBufferHeight,
+ backBufferFormat,
+ m_options
+ ));
+ }
}
// Handle color space settings for HDR
@@ -512,7 +520,7 @@ void DeviceResources::HandleDeviceLost()
m_d3dDevice.Reset();
m_dxgiFactory.Reset();
- CreateDeviceResources();
+ CreateDeviceResources(m_fullScreenMode);
CreateWindowSizeDependentResources();
if (m_deviceNotify)
diff --git a/Common/DeviceResources.h b/Common/DeviceResources.h
index a97ca7d..40e6f23 100644
--- a/Common/DeviceResources.h
+++ b/Common/DeviceResources.h
@@ -37,7 +37,7 @@ namespace DX
DeviceResources(DeviceResources const&) = delete;
DeviceResources& operator= (DeviceResources const&) = delete;
- void CreateDeviceResources();
+ void CreateDeviceResources(BOOL fullScreenMode);
void CreateWindowSizeDependentResources();
void SetWindow(HWND window, int width, int height) noexcept;
bool WindowSizeChanged(int width, int height);
@@ -147,5 +147,7 @@ namespace DX
// The IDeviceNotify can be held directly as it owns the DeviceResources.
IDeviceNotify* m_deviceNotify;
+
+ BOOL m_fullScreenMode;
};
}
diff --git a/Common/DirectXTK12/.gitignore b/Common/DirectXTK12/.gitignore
new file mode 100644
index 0000000..716bf49
--- /dev/null
+++ b/Common/DirectXTK12/.gitignore
@@ -0,0 +1,29 @@
+*.psess
+*.vsp
+*.log
+*.err
+*.wrn
+*.suo
+*.sdf
+*.user
+*.i
+*.vspscc
+*.opensdf
+*.opendb
+*.ipch
+*.cache
+*.tlog
+*.lastbuildstate
+*.ilk
+*.VC.db
+*.nupkg
+.vs
+Bin
+/Src/Shaders/Compiled/*.inc
+/Src/Shaders/Compiled/*.pdb
+/ipch
+/Tests
+/Testing
+/wiki
+/out
+/CMakeUserPresets.json
diff --git a/Common/DirectXTK12/.nuget/directxtk12_desktop_2019.nuspec b/Common/DirectXTK12/.nuget/directxtk12_desktop_2019.nuspec
new file mode 100644
index 0000000..34ddb68
--- /dev/null
+++ b/Common/DirectXTK12/.nuget/directxtk12_desktop_2019.nuspec
@@ -0,0 +1,77 @@
+
+
+
+ directxtk12_desktop_2019
+ 0.0.0-SpecifyVersionOnCommandline
+ DirectX Tool Kit for DirectX 12 (VS 2019/2022 Win32 for Windows 10/11)
+ Microsoft
+ microsoft,directxtk
+ The DirectX Tool Kit (aka DirectXTK) is a collection of helper classes for writing Direct3D 12 code in C++.
+ This version is for Windows desktop applications using Visual Studio 2019 (16.11) or Visual Studio 2022.
+
+Features:
+Audio - low-level audio API using XAudio2
+BufferHelpers - C++ helpers for creating D3D resources from CPU data
+CommonStates - common D3D state combinations
+DDSTextureLoader - light-weight DDS file texture loader
+DescriptorHeap - helper for managing DX12 descriptor heaps
+DirectXHelpers - misc C++ helpers for D3D programming
+Effects - set of built-in shaders for common rendering tasks
+EffectPipelineStateDescription - helper for creating PSOs
+GamePad - gamepad controller helper using Windows.Gaming.Input
+GeometricPrimitive - draws basic shapes such as cubes and spheres
+GraphicsMemory - helper for managing graphics memory allocation
+Keyboard - keyboard state tracking helper
+Model - draws meshes loaded from .CMO, .SDKMESH, or .VBO files
+Mouse - mouse helper
+PostProcess - set of built-in shaders for common post-processing operations
+PrimitiveBatch - simple and efficient way to draw user primitives
+RenderTargetState - helper for communicating render target requirements when creating PSOs
+ResourceUploadBatch - helper for managing texture resource upload to the GPU
+ScreenGrab - light-weight screen shot saver
+SimpleMath - simplified C++ wrapper for DirectXMath
+SpriteBatch - simple & efficient 2D sprite rendering
+SpriteFont - bitmap based text rendering
+VertexTypes - structures for commonly used vertex data formats
+WICTextureLoader - WIC-based image file texture loader
+ Matches the October 28, 2023 release on GitHub.
+ http://go.microsoft.com/fwlink/?LinkID=615561
+
+ images\icon.jpg
+ docs\README.md
+ MIT
+ false
+ © Microsoft Corporation. All rights reserved.
+ DirectX DirectXTK DirectXTK12 native nativepackage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Common/DirectXTK12/.nuget/directxtk12_desktop_2019.targets b/Common/DirectXTK12/.nuget/directxtk12_desktop_2019.targets
new file mode 100644
index 0000000..e7dc68b
--- /dev/null
+++ b/Common/DirectXTK12/.nuget/directxtk12_desktop_2019.targets
@@ -0,0 +1,35 @@
+
+
+
+
+ Debug
+
+
+ Release
+
+
+ Release
+
+
+ Release
+
+
+
+ $(MSBuildThisFileDirectory)..\..\native\lib\$(PlatformTarget)\$(NuGetConfiguration)
+
+
+
+
+ $(directxtk12-LibPath);%(AdditionalLibraryDirectories)
+ DirectXTK12.lib;%(AdditionalDependencies)
+
+
+
+
+
+ HAS_DIRECTXTK12;%(PreprocessorDefinitions)
+ $(MSBuildThisFileDirectory)..\..\include;%(AdditionalIncludeDirectories)
+
+
+
+
diff --git a/Common/DirectXTK12/.nuget/directxtk12_uwp.nuspec b/Common/DirectXTK12/.nuget/directxtk12_uwp.nuspec
new file mode 100644
index 0000000..36cd9f4
--- /dev/null
+++ b/Common/DirectXTK12/.nuget/directxtk12_uwp.nuspec
@@ -0,0 +1,77 @@
+
+
+
+ directxtk12_uwp
+ 0.0.0-SpecifyVersionOnCommandline
+ DirectX Tool Kit for DirectX 12 (UWP)
+ Microsoft
+ microsoft,directxtk
+ The DirectX Tool Kit (aka DirectXTK) is a collection of helper classes for writing Direct3D 12 code in C++.
+ This version is for Universal Windows Platform apps on Windows 10 / Windows 11 using Visual Studio 2019 (16.11) or Visual Studio 2022.
+
+Features:
+Audio - low-level audio API using XAudio2
+BufferHelpers - C++ helpers for creating D3D resources from CPU data
+CommonStates - common D3D state combinations
+DDSTextureLoader - light-weight DDS file texture loader
+DescriptorHeap - helper for managing DX12 descriptor heaps
+DirectXHelpers - misc C++ helpers for D3D programming
+Effects - set of built-in shaders for common rendering tasks
+EffectPipelineStateDescription - helper for creating PSOs
+GamePad - gamepad controller helper using Windows.Gaming.Input
+GeometricPrimitive - draws basic shapes such as cubes and spheres
+GraphicsMemory - helper for managing graphics memory allocation
+Keyboard - keyboard state tracking helper
+Model - draws meshes loaded from .CMO, .SDKMESH, or .VBO files
+Mouse - mouse helper
+PostProcess - set of built-in shaders for common post-processing operations
+PrimitiveBatch - simple and efficient way to draw user primitives
+RenderTargetState - helper for communicating render target requirements when creating PSOs
+ResourceUploadBatch - helper for managing texture resource upload to the GPU
+ScreenGrab - light-weight screen shot saver
+SimpleMath - simplified C++ wrapper for DirectXMath
+SpriteBatch - simple & efficient 2D sprite rendering
+SpriteFont - bitmap based text rendering
+VertexTypes - structures for commonly used vertex data formats
+WICTextureLoader - WIC-based image file texture loader
+ Matches the October 28, 2023 release on GitHub.
+ http://go.microsoft.com/fwlink/?LinkID=615561
+
+ images\icon.jpg
+ docs\README.md
+ MIT
+ false
+ © Microsoft Corporation. All rights reserved.
+ DirectX DirectXTK DirectXTK12 native nativepackage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Common/DirectXTK12/.nuget/directxtk12_uwp.targets b/Common/DirectXTK12/.nuget/directxtk12_uwp.targets
new file mode 100644
index 0000000..e7dc68b
--- /dev/null
+++ b/Common/DirectXTK12/.nuget/directxtk12_uwp.targets
@@ -0,0 +1,35 @@
+
+
+
+
+ Debug
+
+
+ Release
+
+
+ Release
+
+
+ Release
+
+
+
+ $(MSBuildThisFileDirectory)..\..\native\lib\$(PlatformTarget)\$(NuGetConfiguration)
+
+
+
+
+ $(directxtk12-LibPath);%(AdditionalLibraryDirectories)
+ DirectXTK12.lib;%(AdditionalDependencies)
+
+
+
+
+
+ HAS_DIRECTXTK12;%(PreprocessorDefinitions)
+ $(MSBuildThisFileDirectory)..\..\include;%(AdditionalIncludeDirectories)
+
+
+
+
diff --git a/Common/DirectXTK12/.nuget/icon.jpg b/Common/DirectXTK12/.nuget/icon.jpg
new file mode 100644
index 0000000..08fe1fa
Binary files /dev/null and b/Common/DirectXTK12/.nuget/icon.jpg differ
diff --git a/Common/DirectXTK12/Audio/AudioEngine.cpp b/Common/DirectXTK12/Audio/AudioEngine.cpp
new file mode 100644
index 0000000..6dc8042
--- /dev/null
+++ b/Common/DirectXTK12/Audio/AudioEngine.cpp
@@ -0,0 +1,1922 @@
+//--------------------------------------------------------------------------------------
+// File: AudioEngine.cpp
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+// http://go.microsoft.com/fwlink/?LinkId=248929
+// http://go.microsoft.com/fwlink/?LinkID=615561
+//--------------------------------------------------------------------------------------
+
+#include "pch.h"
+#include "Audio.h"
+#include "SoundCommon.h"
+
+#include
+
+using namespace DirectX;
+using Microsoft::WRL::ComPtr;
+
+//#define VERBOSE_TRACE
+
+#ifdef VERBOSE_TRACE
+#pragma message("NOTE: Verbose tracing enabled")
+#endif
+
+namespace
+{
+ struct EngineCallback : public IXAudio2EngineCallback
+ {
+ EngineCallback() noexcept(false)
+ {
+ mCriticalError.reset(CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE));
+ if (!mCriticalError)
+ {
+ throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "CreateEventEx");
+ }
+ }
+
+ EngineCallback(EngineCallback&&) = default;
+ EngineCallback& operator= (EngineCallback&&) = default;
+
+ EngineCallback(EngineCallback const&) = delete;
+ EngineCallback& operator= (EngineCallback const&) = delete;
+
+ virtual ~EngineCallback() = default;
+
+ STDMETHOD_(void, OnProcessingPassStart) () override {}
+ STDMETHOD_(void, OnProcessingPassEnd)() override {}
+
+ STDMETHOD_(void, OnCriticalError) (THIS_ HRESULT error)
+ {
+ #ifndef _DEBUG
+ UNREFERENCED_PARAMETER(error);
+ #endif
+ DebugTrace("ERROR: AudioEngine encountered critical error (%08X)\n", static_cast(error));
+ SetEvent(mCriticalError.get());
+ }
+
+ ScopedHandle mCriticalError;
+ };
+
+ struct VoiceCallback : public IXAudio2VoiceCallback
+ {
+ VoiceCallback() noexcept(false)
+ {
+ mBufferEnd.reset(CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE));
+ if (!mBufferEnd)
+ {
+ throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "CreateEventEx");
+ }
+ }
+
+ VoiceCallback(VoiceCallback&&) = default;
+ VoiceCallback& operator=(VoiceCallback&&) = default;
+
+ VoiceCallback(const VoiceCallback&) = delete;
+ VoiceCallback& operator=(const VoiceCallback&) = delete;
+
+ virtual ~VoiceCallback()
+ {
+ }
+
+ STDMETHOD_(void, OnVoiceProcessingPassStart) (UINT32) override {}
+ STDMETHOD_(void, OnVoiceProcessingPassEnd)() override {}
+ STDMETHOD_(void, OnStreamEnd)() override {}
+ STDMETHOD_(void, OnBufferStart)(void*) override {}
+
+ STDMETHOD_(void, OnBufferEnd)(void* context) override
+ {
+ if (context)
+ {
+ auto inotify = static_cast(context);
+ inotify->OnBufferEnd();
+ SetEvent(mBufferEnd.get());
+ }
+ }
+
+ STDMETHOD_(void, OnLoopEnd)(void*) override {}
+ STDMETHOD_(void, OnVoiceError)(void*, HRESULT) override {}
+
+ ScopedHandle mBufferEnd;
+ };
+
+ static const XAUDIO2FX_REVERB_I3DL2_PARAMETERS gReverbPresets[] =
+ {
+ XAUDIO2FX_I3DL2_PRESET_DEFAULT, // Reverb_Off
+ XAUDIO2FX_I3DL2_PRESET_DEFAULT, // Reverb_Default
+ XAUDIO2FX_I3DL2_PRESET_GENERIC, // Reverb_Generic
+ XAUDIO2FX_I3DL2_PRESET_FOREST, // Reverb_Forest
+ XAUDIO2FX_I3DL2_PRESET_PADDEDCELL, // Reverb_PaddedCell
+ XAUDIO2FX_I3DL2_PRESET_ROOM, // Reverb_Room
+ XAUDIO2FX_I3DL2_PRESET_BATHROOM, // Reverb_Bathroom
+ XAUDIO2FX_I3DL2_PRESET_LIVINGROOM, // Reverb_LivingRoom
+ XAUDIO2FX_I3DL2_PRESET_STONEROOM, // Reverb_StoneRoom
+ XAUDIO2FX_I3DL2_PRESET_AUDITORIUM, // Reverb_Auditorium
+ XAUDIO2FX_I3DL2_PRESET_CONCERTHALL, // Reverb_ConcertHall
+ XAUDIO2FX_I3DL2_PRESET_CAVE, // Reverb_Cave
+ XAUDIO2FX_I3DL2_PRESET_ARENA, // Reverb_Arena
+ XAUDIO2FX_I3DL2_PRESET_HANGAR, // Reverb_Hangar
+ XAUDIO2FX_I3DL2_PRESET_CARPETEDHALLWAY, // Reverb_CarpetedHallway
+ XAUDIO2FX_I3DL2_PRESET_HALLWAY, // Reverb_Hallway
+ XAUDIO2FX_I3DL2_PRESET_STONECORRIDOR, // Reverb_StoneCorridor
+ XAUDIO2FX_I3DL2_PRESET_ALLEY, // Reverb_Alley
+ XAUDIO2FX_I3DL2_PRESET_CITY, // Reverb_City
+ XAUDIO2FX_I3DL2_PRESET_MOUNTAINS, // Reverb_Mountains
+ XAUDIO2FX_I3DL2_PRESET_QUARRY, // Reverb_Quarry
+ XAUDIO2FX_I3DL2_PRESET_PLAIN, // Reverb_Plain
+ XAUDIO2FX_I3DL2_PRESET_PARKINGLOT, // Reverb_ParkingLot
+ XAUDIO2FX_I3DL2_PRESET_SEWERPIPE, // Reverb_SewerPipe
+ XAUDIO2FX_I3DL2_PRESET_UNDERWATER, // Reverb_Underwater
+ XAUDIO2FX_I3DL2_PRESET_SMALLROOM, // Reverb_SmallRoom
+ XAUDIO2FX_I3DL2_PRESET_MEDIUMROOM, // Reverb_MediumRoom
+ XAUDIO2FX_I3DL2_PRESET_LARGEROOM, // Reverb_LargeRoom
+ XAUDIO2FX_I3DL2_PRESET_MEDIUMHALL, // Reverb_MediumHall
+ XAUDIO2FX_I3DL2_PRESET_LARGEHALL, // Reverb_LargeHall
+ XAUDIO2FX_I3DL2_PRESET_PLATE, // Reverb_Plate
+ };
+
+ inline unsigned int makeVoiceKey(_In_ const WAVEFORMATEX* wfx) noexcept
+ {
+ assert(IsValid(wfx));
+
+ if (wfx->nChannels > 0x7F)
+ return 0;
+
+ // This hash does not use nSamplesPerSec because voice reuse can change the source sample rate.
+
+ // nAvgBytesPerSec and nBlockAlign are derived from other values in XAudio2 supported formats.
+
+ union KeyGen
+ {
+ struct
+ {
+ unsigned int tag : 9;
+ unsigned int channels : 7;
+ unsigned int bitsPerSample : 8;
+ } pcm;
+
+ struct
+ {
+ unsigned int tag : 9;
+ unsigned int channels : 7;
+ unsigned int samplesPerBlock : 16;
+ } adpcm;
+
+ #ifdef DIRECTX_ENABLE_XMA2
+ struct
+ {
+ unsigned int tag : 9;
+ unsigned int channels : 7;
+ unsigned int encoderVersion : 8;
+ } xma;
+ #endif
+
+ unsigned int key;
+ } result;
+
+ static_assert(sizeof(KeyGen) == sizeof(unsigned int), "KeyGen is invalid");
+
+ result.key = 0;
+
+ if (wfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
+ {
+ // We reuse EXTENSIBLE only if it is equivalent to the standard form
+ auto wfex = reinterpret_cast(wfx);
+ if (wfex->Samples.wValidBitsPerSample != 0 && wfex->Samples.wValidBitsPerSample != wfx->wBitsPerSample)
+ return 0;
+
+ if (wfex->dwChannelMask != 0 && wfex->dwChannelMask != GetDefaultChannelMask(wfx->nChannels))
+ return 0;
+ }
+
+ const uint32_t tag = GetFormatTag(wfx);
+ switch (tag)
+ {
+ case WAVE_FORMAT_PCM:
+ static_assert(WAVE_FORMAT_PCM < 0x1ff, "KeyGen tag is too small");
+ result.pcm.tag = WAVE_FORMAT_PCM;
+ result.pcm.channels = wfx->nChannels;
+ result.pcm.bitsPerSample = wfx->wBitsPerSample;
+ break;
+
+ case WAVE_FORMAT_IEEE_FLOAT:
+ static_assert(WAVE_FORMAT_IEEE_FLOAT < 0x1ff, "KeyGen tag is too small");
+
+ if (wfx->wBitsPerSample != 32)
+ return 0;
+
+ result.pcm.tag = WAVE_FORMAT_IEEE_FLOAT;
+ result.pcm.channels = wfx->nChannels;
+ result.pcm.bitsPerSample = 32;
+ break;
+
+ case WAVE_FORMAT_ADPCM:
+ static_assert(WAVE_FORMAT_ADPCM < 0x1ff, "KeyGen tag is too small");
+ result.adpcm.tag = WAVE_FORMAT_ADPCM;
+ result.adpcm.channels = wfx->nChannels;
+
+ {
+ auto wfadpcm = reinterpret_cast(wfx);
+ result.adpcm.samplesPerBlock = wfadpcm->wSamplesPerBlock;
+ }
+ break;
+
+ #ifdef DIRECTX_ENABLE_XMA2
+ case WAVE_FORMAT_XMA2:
+ static_assert(WAVE_FORMAT_XMA2 < 0x1ff, "KeyGen tag is too small");
+ result.xma.tag = WAVE_FORMAT_XMA2;
+ result.xma.channels = wfx->nChannels;
+
+ {
+ auto xmaFmt = reinterpret_cast(wfx);
+
+ if ((xmaFmt->LoopBegin > 0)
+ || (xmaFmt->PlayBegin > 0))
+ return 0;
+
+ result.xma.encoderVersion = xmaFmt->EncoderVersion;
+ }
+ break;
+ #endif
+
+ default:
+ return 0;
+ }
+
+ return result.key;
+ }
+
+ void GetDeviceOutputFormat(const wchar_t* deviceId, WAVEFORMATEX& wfx);
+}
+
+static_assert(static_cast(std::size(gReverbPresets)) == Reverb_MAX, "AUDIO_ENGINE_REVERB enum mismatch");
+
+
+//======================================================================================
+// AudioEngine
+//======================================================================================
+
+#define SAFE_DESTROY_VOICE(voice) if ( voice ) { voice->DestroyVoice(); voice = nullptr; }
+
+#ifdef __clang__
+#pragma clang diagnostic ignored "-Wextra-semi-stmt"
+#endif
+
+// Internal object implementation class.
+class AudioEngine::Impl
+{
+public:
+ Impl() noexcept :
+ mMasterVoice(nullptr),
+ mReverbVoice(nullptr),
+ masterChannelMask(0),
+ masterChannels(0),
+ masterRate(0),
+ defaultRate(44100),
+ maxVoiceOneshots(SIZE_MAX),
+ maxVoiceInstances(SIZE_MAX),
+ mMasterVolume(1.f),
+ mX3DAudio{},
+ mCriticalError(false),
+ mReverbEnabled(false),
+ mEngineFlags(AudioEngine_Default),
+ mOutputFormat{},
+ mCategory(AudioCategory_GameEffects),
+ mVoiceInstances(0)
+ {
+ }
+
+ ~Impl() = default;
+
+ Impl(Impl&&) = default;
+ Impl& operator= (Impl&&) = default;
+
+ Impl(Impl const&) = delete;
+ Impl& operator= (Impl const&) = delete;
+
+ HRESULT Initialize(AUDIO_ENGINE_FLAGS flags,
+ _In_opt_ const WAVEFORMATEX* wfx,
+ _In_opt_z_ const wchar_t* deviceId,
+ AUDIO_STREAM_CATEGORY category);
+
+ HRESULT Reset(_In_opt_ const WAVEFORMATEX* wfx, _In_opt_z_ const wchar_t* deviceId);
+
+ void SetSilentMode();
+
+ void Shutdown() noexcept;
+
+ bool Update();
+
+ void SetReverb(_In_opt_ const XAUDIO2FX_REVERB_PARAMETERS* native) noexcept;
+
+ void SetMasteringLimit(int release, int loudness);
+
+ AudioStatistics GetStatistics() const;
+
+ void TrimVoicePool();
+
+ void AllocateVoice(_In_ const WAVEFORMATEX* wfx,
+ SOUND_EFFECT_INSTANCE_FLAGS flags, bool oneshot,
+ _Outptr_result_maybenull_ IXAudio2SourceVoice** voice);
+ void DestroyVoice(_In_ IXAudio2SourceVoice* voice) noexcept;
+
+ void RegisterNotify(_In_ IVoiceNotify* notify, bool usesUpdate);
+ void UnregisterNotify(_In_ IVoiceNotify* notify, bool oneshots, bool usesUpdate);
+
+ ComPtr xaudio2;
+ IXAudio2MasteringVoice* mMasterVoice;
+ IXAudio2SubmixVoice* mReverbVoice;
+
+ uint32_t masterChannelMask;
+ uint32_t masterChannels;
+ uint32_t masterRate;
+
+ int defaultRate;
+ size_t maxVoiceOneshots;
+ size_t maxVoiceInstances;
+ float mMasterVolume;
+
+ X3DAUDIO_HANDLE mX3DAudio;
+
+ bool mCriticalError;
+ bool mReverbEnabled;
+
+ AUDIO_ENGINE_FLAGS mEngineFlags;
+ WAVEFORMATEX mOutputFormat;
+
+private:
+ using notifylist_t = std::set;
+ using oneshotlist_t = std::list>;
+ using voicepool_t = std::unordered_multimap;
+
+ AUDIO_STREAM_CATEGORY mCategory;
+ ComPtr mReverbEffect;
+ ComPtr mVolumeLimiter;
+ oneshotlist_t mOneShots;
+ voicepool_t mVoicePool;
+ notifylist_t mNotifyObjects;
+ notifylist_t mNotifyUpdates;
+ size_t mVoiceInstances;
+ VoiceCallback mVoiceCallback;
+ EngineCallback mEngineCallback;
+};
+
+
+_Use_decl_annotations_
+HRESULT AudioEngine::Impl::Initialize(
+ AUDIO_ENGINE_FLAGS flags,
+ const WAVEFORMATEX* wfx,
+ const wchar_t* deviceId,
+ AUDIO_STREAM_CATEGORY category)
+{
+ mEngineFlags = flags;
+ mCategory = category;
+
+ return Reset(wfx, deviceId);
+}
+
+
+_Use_decl_annotations_
+HRESULT AudioEngine::Impl::Reset(const WAVEFORMATEX* wfx, const wchar_t* deviceId)
+{
+ if (wfx)
+ {
+ if (wfx->wFormatTag != WAVE_FORMAT_PCM)
+ return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
+
+ if (!wfx->nChannels || wfx->nChannels > XAUDIO2_MAX_AUDIO_CHANNELS)
+ return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
+
+ if (wfx->nSamplesPerSec < XAUDIO2_MIN_SAMPLE_RATE || wfx->nSamplesPerSec > XAUDIO2_MAX_SAMPLE_RATE)
+ return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
+
+ // We don't use other data members of WAVEFORMATEX here to describe the device format, so no need to fully validate
+ }
+
+ assert(!xaudio2);
+ assert(mMasterVoice == nullptr);
+ assert(mReverbVoice == nullptr);
+
+ masterChannelMask = masterChannels = masterRate = 0;
+ mOutputFormat = {};
+
+ memset(&mX3DAudio, 0, X3DAUDIO_HANDLE_BYTESIZE);
+
+ mCriticalError = false;
+ mReverbEnabled = false;
+
+ //
+ // Create XAudio2 engine
+ //
+ HRESULT hr = XAudio2Create(xaudio2.ReleaseAndGetAddressOf(), 0u);
+ if (FAILED(hr))
+ return hr;
+
+ if (mEngineFlags & AudioEngine_Debug)
+ {
+ XAUDIO2_DEBUG_CONFIGURATION debug = {};
+ debug.TraceMask = XAUDIO2_LOG_ERRORS | XAUDIO2_LOG_WARNINGS;
+ debug.BreakMask = XAUDIO2_LOG_ERRORS;
+ xaudio2->SetDebugConfiguration(&debug, nullptr);
+ #ifdef USING_XAUDIO2_9
+ DebugTrace("INFO: XAudio 2.9 debugging enabled\n");
+ #else // USING_XAUDIO2_8
+ // To see the trace output, you need to view ETW logs for this application:
+ // Go to Control Panel, Administrative Tools, Event Viewer.
+ // View->Show Analytic and Debug Logs.
+ // Applications and Services Logs / Microsoft / Windows / XAudio2.
+ // Right click on Microsoft Windows XAudio2 debug logging, Properties, then Enable Logging, and hit OK
+ DebugTrace("INFO: XAudio 2.8 debugging enabled\n");
+ #endif
+ }
+
+ if (mEngineFlags & AudioEngine_DisableVoiceReuse)
+ {
+ DebugTrace("INFO: Voice reuse is disabled\n");
+ }
+
+ hr = xaudio2->RegisterForCallbacks(&mEngineCallback);
+ if (FAILED(hr))
+ {
+ xaudio2.Reset();
+ return hr;
+ }
+
+ //
+ // Create mastering voice for device
+ //
+ hr = xaudio2->CreateMasteringVoice(&mMasterVoice,
+ (wfx) ? wfx->nChannels : 0u /*XAUDIO2_DEFAULT_CHANNELS */,
+ (wfx) ? wfx->nSamplesPerSec : 0u /* XAUDIO2_DEFAULT_SAMPLERATE */,
+ 0u, deviceId, nullptr, mCategory);
+ if (FAILED(hr))
+ {
+ xaudio2.Reset();
+ return hr;
+ }
+
+ DWORD dwChannelMask;
+ hr = mMasterVoice->GetChannelMask(&dwChannelMask);
+ if (FAILED(hr))
+ {
+ SAFE_DESTROY_VOICE(mMasterVoice);
+ xaudio2.Reset();
+ return hr;
+ }
+
+ XAUDIO2_VOICE_DETAILS details;
+ mMasterVoice->GetVoiceDetails(&details);
+
+ masterChannelMask = dwChannelMask;
+ masterChannels = details.InputChannels;
+ masterRate = details.InputSampleRate;
+
+ DebugTrace("INFO: mastering voice has %u channels, %u sample rate, %08X channel mask\n",
+ masterChannels, masterRate, masterChannelMask);
+
+ if (mMasterVolume != 1.f)
+ {
+ hr = mMasterVoice->SetVolume(mMasterVolume);
+ if (FAILED(hr))
+ {
+ SAFE_DESTROY_VOICE(mMasterVoice);
+ xaudio2.Reset();
+ return hr;
+ }
+ }
+
+ mOutputFormat.wFormatTag = WAVE_FORMAT_PCM;
+ mOutputFormat.nChannels = static_cast(details.InputChannels);
+ mOutputFormat.nSamplesPerSec = details.InputSampleRate;
+ mOutputFormat.wBitsPerSample = 16;
+ GetDeviceOutputFormat(deviceId, mOutputFormat);
+
+ //
+ // Setup mastering volume limiter (optional)
+ //
+ if (mEngineFlags & AudioEngine_UseMasteringLimiter)
+ {
+ FXMASTERINGLIMITER_PARAMETERS params = {};
+ params.Release = FXMASTERINGLIMITER_DEFAULT_RELEASE;
+ params.Loudness = FXMASTERINGLIMITER_DEFAULT_LOUDNESS;
+
+ hr = CreateFX(__uuidof(FXMasteringLimiter), mVolumeLimiter.ReleaseAndGetAddressOf(), ¶ms, sizeof(params));
+ if (FAILED(hr))
+ {
+ SAFE_DESTROY_VOICE(mMasterVoice);
+ xaudio2.Reset();
+ return hr;
+ }
+
+ XAUDIO2_EFFECT_DESCRIPTOR desc = {};
+ desc.InitialState = TRUE;
+ desc.OutputChannels = masterChannels;
+ desc.pEffect = mVolumeLimiter.Get();
+
+ XAUDIO2_EFFECT_CHAIN chain = { 1, &desc };
+ hr = mMasterVoice->SetEffectChain(&chain);
+ if (FAILED(hr))
+ {
+ SAFE_DESTROY_VOICE(mMasterVoice);
+ mVolumeLimiter.Reset();
+ xaudio2.Reset();
+ return hr;
+ }
+
+ DebugTrace("INFO: Mastering volume limiter enabled\n");
+ }
+
+ //
+ // Setup environmental reverb for 3D audio (optional)
+ //
+ if (mEngineFlags & AudioEngine_EnvironmentalReverb)
+ {
+ hr = XAudio2CreateReverb(mReverbEffect.ReleaseAndGetAddressOf(), 0u);
+ if (FAILED(hr))
+ {
+ SAFE_DESTROY_VOICE(mMasterVoice);
+ mVolumeLimiter.Reset();
+ xaudio2.Reset();
+ return hr;
+ }
+
+ XAUDIO2_EFFECT_DESCRIPTOR effects[] = { { mReverbEffect.Get(), TRUE, 1 } };
+ XAUDIO2_EFFECT_CHAIN effectChain = { 1, effects };
+
+ mReverbEnabled = true;
+
+ hr = xaudio2->CreateSubmixVoice(&mReverbVoice, 1, masterRate,
+ (mEngineFlags & AudioEngine_ReverbUseFilters) ? XAUDIO2_VOICE_USEFILTER : 0u, 0u,
+ nullptr, &effectChain);
+ if (FAILED(hr))
+ {
+ SAFE_DESTROY_VOICE(mMasterVoice);
+ mReverbEffect.Reset();
+ mVolumeLimiter.Reset();
+ xaudio2.Reset();
+ return hr;
+ }
+
+ XAUDIO2FX_REVERB_PARAMETERS native;
+ ReverbConvertI3DL2ToNative(&gReverbPresets[Reverb_Default], &native);
+ hr = mReverbVoice->SetEffectParameters(0, &native, sizeof(XAUDIO2FX_REVERB_PARAMETERS));
+ if (FAILED(hr))
+ {
+ SAFE_DESTROY_VOICE(mReverbVoice);
+ SAFE_DESTROY_VOICE(mMasterVoice);
+ mReverbEffect.Reset();
+ mVolumeLimiter.Reset();
+ xaudio2.Reset();
+ return hr;
+ }
+
+ DebugTrace("INFO: I3DL2 reverb effect enabled for 3D positional audio\n");
+ }
+
+ //
+ // Setup 3D audio
+ //
+ constexpr float SPEEDOFSOUND = X3DAUDIO_SPEED_OF_SOUND;
+
+ hr = X3DAudioInitialize(masterChannelMask, SPEEDOFSOUND, mX3DAudio);
+ if (FAILED(hr))
+ {
+ SAFE_DESTROY_VOICE(mReverbVoice);
+ SAFE_DESTROY_VOICE(mMasterVoice);
+ mReverbEffect.Reset();
+ mVolumeLimiter.Reset();
+ xaudio2.Reset();
+ return hr;
+ }
+
+ //
+ // Inform any notify objects we are ready to go again
+ //
+ for (auto it : mNotifyObjects)
+ {
+ assert(it != nullptr);
+ it->OnReset();
+ }
+
+ return S_OK;
+}
+
+
+void AudioEngine::Impl::SetSilentMode()
+{
+ for (auto it : mNotifyObjects)
+ {
+ assert(it != nullptr);
+ it->OnCriticalError();
+ }
+
+ for (auto& it : mOneShots)
+ {
+ assert(it.second != nullptr);
+ it.second->DestroyVoice();
+ }
+ mOneShots.clear();
+
+ for (auto& it : mVoicePool)
+ {
+ assert(it.second != nullptr);
+ it.second->DestroyVoice();
+ }
+ mVoicePool.clear();
+
+ mVoiceInstances = 0;
+
+ SAFE_DESTROY_VOICE(mReverbVoice);
+ SAFE_DESTROY_VOICE(mMasterVoice);
+
+ mReverbEffect.Reset();
+ mVolumeLimiter.Reset();
+ xaudio2.Reset();
+}
+
+
+void AudioEngine::Impl::Shutdown() noexcept
+{
+ for (auto it : mNotifyObjects)
+ {
+ assert(it != nullptr);
+ it->OnDestroyEngine();
+ }
+
+ if (xaudio2)
+ {
+ xaudio2->UnregisterForCallbacks(&mEngineCallback);
+
+ xaudio2->StopEngine();
+
+ for (auto& it : mOneShots)
+ {
+ assert(it.second != nullptr);
+ it.second->DestroyVoice();
+ }
+ mOneShots.clear();
+
+ for (auto& it : mVoicePool)
+ {
+ assert(it.second != nullptr);
+ it.second->DestroyVoice();
+ }
+ mVoicePool.clear();
+
+ mVoiceInstances = 0;
+
+ SAFE_DESTROY_VOICE(mReverbVoice);
+ SAFE_DESTROY_VOICE(mMasterVoice);
+
+ mReverbEffect.Reset();
+ mVolumeLimiter.Reset();
+ xaudio2.Reset();
+
+ masterChannelMask = masterChannels = masterRate = 0;
+ mOutputFormat = {};
+
+ mCriticalError = false;
+ mReverbEnabled = false;
+
+ memset(&mX3DAudio, 0, X3DAUDIO_HANDLE_BYTESIZE);
+ }
+}
+
+
+bool AudioEngine::Impl::Update()
+{
+ if (!xaudio2)
+ return false;
+
+ HANDLE events[2] = { mEngineCallback.mCriticalError.get(), mVoiceCallback.mBufferEnd.get() };
+ switch (WaitForMultipleObjectsEx(static_cast(std::size(events)), events, FALSE, 0, FALSE))
+ {
+ default:
+ case WAIT_TIMEOUT:
+ break;
+
+ case WAIT_OBJECT_0: // OnCritialError
+ mCriticalError = true;
+
+ SetSilentMode();
+ return false;
+
+ case WAIT_OBJECT_0 + 1: // OnBufferEnd
+ // Scan for completed one-shot voices
+ for (auto it = mOneShots.begin(); it != mOneShots.end(); )
+ {
+ assert(it->second != nullptr);
+
+ XAUDIO2_VOICE_STATE xstate;
+ it->second->GetState(&xstate, XAUDIO2_VOICE_NOSAMPLESPLAYED);
+
+ if (!xstate.BuffersQueued)
+ {
+ std::ignore = it->second->Stop(0);
+ if (it->first)
+ {
+ // Put voice back into voice pool for reuse since it has a non-zero voiceKey
+ #ifdef VERBOSE_TRACE
+ DebugTrace("INFO: One-shot voice being saved for reuse (%08X)\n", it->first);
+ #endif
+ voicepool_t::value_type v(it->first, it->second);
+ mVoicePool.emplace(v);
+ }
+ else
+ {
+ // Voice is to be destroyed rather than reused
+ #ifdef VERBOSE_TRACE
+ DebugTrace("INFO: Destroying one-shot voice\n");
+ #endif
+ it->second->DestroyVoice();
+ }
+ it = mOneShots.erase(it);
+ }
+ else
+ ++it;
+ }
+ break;
+
+ case WAIT_FAILED:
+ throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "WaitForMultipleObjectsEx");
+ }
+
+ //
+ // Inform any notify objects of updates
+ //
+ for (auto it : mNotifyUpdates)
+ {
+ assert(it != nullptr);
+ it->OnUpdate();
+ }
+
+ return true;
+}
+
+
+_Use_decl_annotations_
+void AudioEngine::Impl::SetReverb(const XAUDIO2FX_REVERB_PARAMETERS* native) noexcept
+{
+ if (!mReverbVoice)
+ return;
+
+ if (native)
+ {
+ if (!mReverbEnabled)
+ {
+ mReverbEnabled = true;
+ std::ignore = mReverbVoice->EnableEffect(0);
+ }
+
+ std::ignore = mReverbVoice->SetEffectParameters(0, native, sizeof(XAUDIO2FX_REVERB_PARAMETERS));
+ }
+ else if (mReverbEnabled)
+ {
+ mReverbEnabled = false;
+ std::ignore = mReverbVoice->DisableEffect(0);
+ }
+}
+
+
+void AudioEngine::Impl::SetMasteringLimit(int release, int loudness)
+{
+ if (!mVolumeLimiter || !mMasterVoice)
+ return;
+
+ if ((release < FXMASTERINGLIMITER_MIN_RELEASE) || (release > FXMASTERINGLIMITER_MAX_RELEASE))
+ throw std::out_of_range("AudioEngine::SetMasteringLimit");
+
+ if ((loudness < FXMASTERINGLIMITER_MIN_LOUDNESS) || (loudness > FXMASTERINGLIMITER_MAX_LOUDNESS))
+ throw std::out_of_range("AudioEngine::SetMasteringLimit");
+
+ FXMASTERINGLIMITER_PARAMETERS params = {};
+ params.Release = static_cast(release);
+ params.Loudness = static_cast(loudness);
+
+ HRESULT hr = mMasterVoice->SetEffectParameters(0, ¶ms, sizeof(params));
+ ThrowIfFailed(hr);
+}
+
+
+AudioStatistics AudioEngine::Impl::GetStatistics() const
+{
+ AudioStatistics stats = {};
+
+ stats.allocatedVoices = stats.allocatedVoicesOneShot = mOneShots.size() + mVoicePool.size();
+ stats.allocatedVoicesIdle = mVoicePool.size();
+
+ for (const auto it : mNotifyObjects)
+ {
+ assert(it != nullptr);
+ it->GatherStatistics(stats);
+ }
+
+ assert(stats.allocatedVoices == (mOneShots.size() + mVoicePool.size() + mVoiceInstances));
+
+ return stats;
+}
+
+
+void AudioEngine::Impl::TrimVoicePool()
+{
+ for (auto it : mNotifyObjects)
+ {
+ assert(it != nullptr);
+ it->OnTrim();
+ }
+
+ for (auto& it : mVoicePool)
+ {
+ assert(it.second != nullptr);
+ it.second->DestroyVoice();
+ }
+ mVoicePool.clear();
+}
+
+
+_Use_decl_annotations_
+void AudioEngine::Impl::AllocateVoice(
+ const WAVEFORMATEX* wfx,
+ SOUND_EFFECT_INSTANCE_FLAGS flags,
+ bool oneshot,
+ IXAudio2SourceVoice** voice)
+{
+ if (!wfx)
+ throw std::invalid_argument("Wave format is required\n");
+
+ // No need to call IsValid on wfx because CreateSourceVoice will do that
+
+ if (!voice)
+ throw std::invalid_argument("Voice pointer must be non-null");
+
+ *voice = nullptr;
+
+ if (!xaudio2 || mCriticalError)
+ return;
+
+#ifndef NDEBUG
+ const float maxFrequencyRatio = XAudio2SemitonesToFrequencyRatio(12);
+ assert(maxFrequencyRatio <= XAUDIO2_DEFAULT_FREQ_RATIO);
+#endif
+
+ unsigned int voiceKey = 0;
+ if (oneshot)
+ {
+ if (flags & (SoundEffectInstance_Use3D | SoundEffectInstance_ReverbUseFilters | SoundEffectInstance_NoSetPitch))
+ {
+ DebugTrace((flags & SoundEffectInstance_NoSetPitch)
+ ? "ERROR: One-shot voices must support pitch-shifting for voice reuse\n"
+ : "ERROR: One-use voices cannot use 3D positional audio\n");
+ throw std::invalid_argument("Invalid flags for one-shot voice");
+ }
+
+ #ifdef VERBOSE_TRACE
+ if (wfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
+ {
+ DebugTrace("INFO: Requesting one-shot: Format Tag EXTENSIBLE %u, %u channels, %u-bit, %u blkalign, %u Hz\n",
+ GetFormatTag(wfx), wfx->nChannels, wfx->wBitsPerSample, wfx->nBlockAlign, wfx->nSamplesPerSec);
+ }
+ else
+ {
+ DebugTrace("INFO: Requesting one-shot: Format Tag %u, %u channels, %u-bit, %u blkalign, %u Hz\n",
+ wfx->wFormatTag, wfx->nChannels, wfx->wBitsPerSample, wfx->nBlockAlign, wfx->nSamplesPerSec);
+ }
+ #endif
+
+ if (!(mEngineFlags & AudioEngine_DisableVoiceReuse))
+ {
+ voiceKey = makeVoiceKey(wfx);
+ if (voiceKey != 0)
+ {
+ auto it = mVoicePool.find(voiceKey);
+ if (it != mVoicePool.end())
+ {
+ // Found a matching (stopped) voice to reuse
+ assert(it->second != nullptr);
+ *voice = it->second;
+ mVoicePool.erase(it);
+
+ // Reset any volume/pitch-shifting
+ HRESULT hr = (*voice)->SetVolume(1.f);
+ ThrowIfFailed(hr);
+
+ hr = (*voice)->SetFrequencyRatio(1.f);
+ ThrowIfFailed(hr);
+
+ if (wfx->nChannels == 1 || wfx->nChannels == 2)
+ {
+ // Reset any panning
+ float matrix[16] = {};
+ ComputePan(0.f, wfx->nChannels, matrix);
+
+ hr = (*voice)->SetOutputMatrix(nullptr, wfx->nChannels, masterChannels, matrix);
+ ThrowIfFailed(hr);
+ }
+ }
+ else if ((mVoicePool.size() + mOneShots.size() + 1) >= maxVoiceOneshots)
+ {
+ DebugTrace("WARNING: Too many one-shot voices in use (%zu + %zu >= %zu); one-shot not played\n",
+ mVoicePool.size(), mOneShots.size() + 1, maxVoiceOneshots);
+ return;
+ }
+ else
+ {
+ // makeVoiceKey already constrained the supported wfx formats to those supported for reuse
+
+ char buff[64] = {};
+ auto wfmt = reinterpret_cast(buff);
+
+ const uint32_t tag = GetFormatTag(wfx);
+ switch (tag)
+ {
+ case WAVE_FORMAT_PCM:
+ CreateIntegerPCM(wfmt, defaultRate, wfx->nChannels, wfx->wBitsPerSample);
+ break;
+
+ case WAVE_FORMAT_IEEE_FLOAT:
+ CreateFloatPCM(wfmt, defaultRate, wfx->nChannels);
+ break;
+
+ case WAVE_FORMAT_ADPCM:
+ {
+ auto wfadpcm = reinterpret_cast(wfx);
+ CreateADPCM(wfmt, sizeof(buff), defaultRate, wfx->nChannels, wfadpcm->wSamplesPerBlock);
+ }
+ break;
+
+ #ifdef DIRECTX_ENABLE_XMA2
+ case WAVE_FORMAT_XMA2:
+ CreateXMA2(wfmt, sizeof(buff), defaultRate, wfx->nChannels, 65536, 2, 0);
+ break;
+ #endif
+ }
+
+ #ifdef VERBOSE_TRACE
+ DebugTrace("INFO: Allocate reuse voice: Format Tag %u, %u channels, %u-bit, %u blkalign, %u Hz\n",
+ wfmt->wFormatTag, wfmt->nChannels, wfmt->wBitsPerSample, wfmt->nBlockAlign, wfmt->nSamplesPerSec);
+ #endif
+
+ assert(voiceKey == makeVoiceKey(wfmt));
+
+ HRESULT hr = xaudio2->CreateSourceVoice(voice, wfmt, 0, XAUDIO2_DEFAULT_FREQ_RATIO, &mVoiceCallback, nullptr, nullptr);
+ if (FAILED(hr))
+ {
+ DebugTrace("ERROR: CreateSourceVoice (reuse) failed with error %08X\n", static_cast(hr));
+ throw std::runtime_error("CreateSourceVoice");
+ }
+ }
+
+ assert(*voice != nullptr);
+ HRESULT hr = (*voice)->SetSourceSampleRate(wfx->nSamplesPerSec);
+ if (FAILED(hr))
+ {
+ DebugTrace("ERROR: SetSourceSampleRate failed with error %08X\n", static_cast(hr));
+ throw std::runtime_error("SetSourceSampleRate");
+ }
+ }
+ }
+ }
+
+ if (!*voice)
+ {
+ if (oneshot)
+ {
+ if ((mVoicePool.size() + mOneShots.size() + 1) >= maxVoiceOneshots)
+ {
+ DebugTrace("WARNING: Too many one-shot voices in use (%zu + %zu >= %zu); one-shot not played; see TrimVoicePool\n",
+ mVoicePool.size(), mOneShots.size() + 1, maxVoiceOneshots);
+ return;
+ }
+ }
+ else if ((mVoiceInstances + 1) >= maxVoiceInstances)
+ {
+ DebugTrace("ERROR: Too many instance voices (%zu >= %zu); see TrimVoicePool\n",
+ mVoiceInstances + 1, maxVoiceInstances);
+ throw std::runtime_error("Too many instance voices");
+ }
+
+ const UINT32 vflags = (flags & SoundEffectInstance_NoSetPitch) ? XAUDIO2_VOICE_NOPITCH : 0u;
+
+ HRESULT hr;
+ if (flags & SoundEffectInstance_Use3D)
+ {
+ XAUDIO2_SEND_DESCRIPTOR sendDescriptors[2] = {};
+ sendDescriptors[0].Flags = sendDescriptors[1].Flags = (flags & SoundEffectInstance_ReverbUseFilters)
+ ? XAUDIO2_SEND_USEFILTER : 0u;
+ sendDescriptors[0].pOutputVoice = mMasterVoice;
+ sendDescriptors[1].pOutputVoice = mReverbVoice;
+ const XAUDIO2_VOICE_SENDS sendList = { mReverbVoice ? 2U : 1U, sendDescriptors };
+
+ #ifdef VERBOSE_TRACE
+ DebugTrace("INFO: Allocate voice 3D: Format Tag %u, %u channels, %u-bit, %u blkalign, %u Hz\n",
+ wfx->wFormatTag, wfx->nChannels, wfx->wBitsPerSample, wfx->nBlockAlign, wfx->nSamplesPerSec);
+ #endif
+
+ hr = xaudio2->CreateSourceVoice(voice, wfx, vflags, XAUDIO2_DEFAULT_FREQ_RATIO, &mVoiceCallback, &sendList, nullptr);
+ }
+ else
+ {
+ #ifdef VERBOSE_TRACE
+ DebugTrace("INFO: Allocate voice: Format Tag %u, %u channels, %u-bit, %u blkalign, %u Hz\n",
+ wfx->wFormatTag, wfx->nChannels, wfx->wBitsPerSample, wfx->nBlockAlign, wfx->nSamplesPerSec);
+ #endif
+
+ hr = xaudio2->CreateSourceVoice(voice, wfx, vflags, XAUDIO2_DEFAULT_FREQ_RATIO, &mVoiceCallback, nullptr, nullptr);
+ }
+
+ if (FAILED(hr))
+ {
+ DebugTrace("ERROR: CreateSourceVoice failed with error %08X\n", static_cast(hr));
+ throw std::runtime_error("CreateSourceVoice");
+ }
+ else if (!oneshot)
+ {
+ ++mVoiceInstances;
+ }
+ }
+
+ if (oneshot)
+ {
+ assert(*voice != nullptr);
+ mOneShots.emplace_back(std::make_pair(voiceKey, *voice));
+ }
+}
+
+
+void AudioEngine::Impl::DestroyVoice(_In_ IXAudio2SourceVoice* voice) noexcept
+{
+ if (!voice)
+ return;
+
+#ifndef NDEBUG
+ for (const auto& it : mOneShots)
+ {
+ if (it.second == voice)
+ {
+ DebugTrace("ERROR: DestroyVoice should not be called for a one-shot voice\n");
+ return;
+ }
+ }
+
+ for (const auto& it : mVoicePool)
+ {
+ if (it.second == voice)
+ {
+ DebugTrace("ERROR: DestroyVoice should not be called for a one-shot voice; see TrimVoicePool\n");
+ return;
+ }
+ }
+#endif
+
+ assert(mVoiceInstances > 0);
+ --mVoiceInstances;
+ voice->DestroyVoice();
+}
+
+
+void AudioEngine::Impl::RegisterNotify(_In_ IVoiceNotify* notify, bool usesUpdate)
+{
+ assert(notify != nullptr);
+ mNotifyObjects.insert(notify);
+
+ if (usesUpdate)
+ {
+ mNotifyUpdates.insert(notify);
+ }
+}
+
+
+void AudioEngine::Impl::UnregisterNotify(_In_ IVoiceNotify* notify, bool usesOneShots, bool usesUpdate)
+{
+ assert(notify != nullptr);
+ mNotifyObjects.erase(notify);
+
+ // Check for any pending one-shots for this notification object
+ if (usesOneShots)
+ {
+ bool setevent = false;
+
+ for (auto& it : mOneShots)
+ {
+ assert(it.second != nullptr);
+
+ XAUDIO2_VOICE_STATE state;
+ it.second->GetState(&state, XAUDIO2_VOICE_NOSAMPLESPLAYED);
+
+ if (state.pCurrentBufferContext == notify)
+ {
+ std::ignore = it.second->Stop(0);
+ std::ignore = it.second->FlushSourceBuffers();
+ setevent = true;
+ }
+ }
+
+ if (setevent)
+ {
+ // Trigger scan on next call to Update...
+ SetEvent(mVoiceCallback.mBufferEnd.get());
+ }
+ }
+
+ if (usesUpdate)
+ {
+ mNotifyUpdates.erase(notify);
+ }
+}
+
+
+//--------------------------------------------------------------------------------------
+// AudioEngine
+//--------------------------------------------------------------------------------------
+
+// Public constructor.
+_Use_decl_annotations_
+AudioEngine::AudioEngine(
+ AUDIO_ENGINE_FLAGS flags,
+ const WAVEFORMATEX* wfx,
+ const wchar_t* deviceId,
+ AUDIO_STREAM_CATEGORY category) noexcept(false)
+ : pImpl(std::make_unique())
+{
+ HRESULT hr = pImpl->Initialize(flags, wfx, deviceId, category);
+ if (FAILED(hr))
+ {
+ const wchar_t* deviceName = (deviceId) ? deviceId : L"default";
+ if (hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND))
+ {
+ if (flags & AudioEngine_ThrowOnNoAudioHW)
+ {
+ DebugTrace("ERROR: AudioEngine found no default audio device\n");
+ throw std::runtime_error("AudioEngineNoAudioHW");
+ }
+ else
+ {
+ DebugTrace("WARNING: AudioEngine found no default audio device; running in 'silent mode'\n");
+ }
+ }
+ else if (hr == AUDCLNT_E_DEVICE_IN_USE)
+ {
+ if (flags & AudioEngine_ThrowOnNoAudioHW)
+ {
+ DebugTrace("ERROR: AudioEngine audio device [%ls] was already in use\n", deviceName);
+ throw std::runtime_error("AudioEngineNoAudioHW");
+ }
+ else
+ {
+ DebugTrace("WARNING: AudioEngine audio device [%ls] already in use; running in 'silent mode'\n", deviceName);
+ }
+ }
+ else
+ {
+ DebugTrace("ERROR: AudioEngine failed (%08X) to initialize using device [%ls]\n",
+ static_cast(hr), deviceName);
+ throw std::runtime_error("AudioEngine");
+ }
+ }
+}
+
+
+// Move ctor/operator.
+AudioEngine::AudioEngine(AudioEngine&&) noexcept = default;
+AudioEngine& AudioEngine::operator= (AudioEngine&&) noexcept = default;
+
+
+// Public destructor.
+AudioEngine::~AudioEngine()
+{
+ if (pImpl)
+ {
+ pImpl->Shutdown();
+ }
+}
+
+
+// Public methods.
+bool AudioEngine::Update()
+{
+ return pImpl->Update();
+}
+
+
+_Use_decl_annotations_
+bool AudioEngine::Reset(const WAVEFORMATEX* wfx, const wchar_t* deviceId)
+{
+ if (pImpl->xaudio2)
+ {
+ DebugTrace("WARNING: Called Reset for active audio graph; going silent in preparation for migration\n");
+ pImpl->SetSilentMode();
+ }
+
+ HRESULT hr = pImpl->Reset(wfx, deviceId);
+ if (FAILED(hr))
+ {
+ const wchar_t* deviceName = (deviceId) ? deviceId : L"default";
+ if (hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND))
+ {
+ if (pImpl->mEngineFlags & AudioEngine_ThrowOnNoAudioHW)
+ {
+ DebugTrace("ERROR: AudioEngine found no default audio device on Reset\n");
+ throw std::runtime_error("AudioEngineNoAudioHW");
+ }
+ else
+ {
+ DebugTrace("WARNING: AudioEngine found no default audio device on Reset; running in 'silent mode'\n");
+ return false;
+ }
+ }
+ else if (hr == AUDCLNT_E_DEVICE_IN_USE)
+ {
+ if (pImpl->mEngineFlags & AudioEngine_ThrowOnNoAudioHW)
+ {
+ DebugTrace("ERROR: AudioEngine failed to initialize using device [%ls] because it was already in use.\n", deviceName);
+ throw std::runtime_error("AudioEngineNoAudioHW");
+ }
+ else
+ {
+ DebugTrace("WARNING: AudioEngine failed to initialize using device [%ls] because it was already in use.\n", deviceName);
+ return false;
+ }
+ }
+ else
+ {
+ DebugTrace("ERROR: AudioEngine failed (%08X) to Reset using device [%ls]\n",
+ static_cast(hr), deviceName);
+ throw std::runtime_error("AudioEngine::Reset");
+ }
+ }
+
+ DebugTrace("INFO: AudioEngine Reset using device [%ls]\n", (deviceId) ? deviceId : L"default");
+
+ return true;
+}
+
+
+void AudioEngine::Suspend() noexcept
+{
+ if (!pImpl->xaudio2)
+ return;
+
+ pImpl->xaudio2->StopEngine();
+}
+
+
+void AudioEngine::Resume()
+{
+ if (!pImpl->xaudio2)
+ return;
+
+ HRESULT hr = pImpl->xaudio2->StartEngine();
+ if (FAILED(hr))
+ {
+ DebugTrace("WARNING: Resume of the audio engine failed; running in 'silent mode'\n");
+ pImpl->SetSilentMode();
+ }
+}
+
+
+float AudioEngine::GetMasterVolume() const noexcept
+{
+ return pImpl->mMasterVolume;
+}
+
+
+void AudioEngine::SetMasterVolume(float volume)
+{
+ assert(volume >= -XAUDIO2_MAX_VOLUME_LEVEL && volume <= XAUDIO2_MAX_VOLUME_LEVEL);
+
+ pImpl->mMasterVolume = volume;
+
+ if (pImpl->mMasterVoice)
+ {
+ HRESULT hr = pImpl->mMasterVoice->SetVolume(volume);
+ ThrowIfFailed(hr);
+ }
+}
+
+
+void AudioEngine::SetReverb(AUDIO_ENGINE_REVERB reverb)
+{
+ if (reverb >= Reverb_MAX)
+ throw std::invalid_argument("reverb parameter is invalid");
+
+ if (reverb == Reverb_Off)
+ {
+ pImpl->SetReverb(nullptr);
+ }
+ else
+ {
+ XAUDIO2FX_REVERB_PARAMETERS native;
+ ReverbConvertI3DL2ToNative(&gReverbPresets[reverb], &native);
+ pImpl->SetReverb(&native);
+ }
+}
+
+
+_Use_decl_annotations_
+void AudioEngine::SetReverb(const XAUDIO2FX_REVERB_PARAMETERS* native)
+{
+ pImpl->SetReverb(native);
+}
+
+
+void AudioEngine::SetMasteringLimit(int release, int loudness)
+{
+ pImpl->SetMasteringLimit(release, loudness);
+}
+
+
+// Public accessors.
+AudioStatistics AudioEngine::GetStatistics() const
+{
+ return pImpl->GetStatistics();
+}
+
+
+WAVEFORMATEXTENSIBLE AudioEngine::GetOutputFormat() const noexcept
+{
+ WAVEFORMATEXTENSIBLE wfx = {};
+
+ if (!pImpl->xaudio2)
+ return wfx;
+
+ wfx.Format = pImpl->mOutputFormat;
+ wfx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
+ wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+
+ wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample;
+ wfx.dwChannelMask = pImpl->masterChannelMask;
+
+ wfx.Format.nBlockAlign = static_cast(wfx.Format.nChannels * wfx.Format.wBitsPerSample / 8);
+ wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
+
+ static const GUID s_wfexBase = { 0x00000000, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 } };
+ memcpy(&wfx.SubFormat, &s_wfexBase, sizeof(GUID));
+ wfx.SubFormat.Data1 = wfx.Format.wFormatTag;
+
+ return wfx;
+}
+
+
+uint32_t AudioEngine::GetChannelMask() const noexcept
+{
+ return pImpl->masterChannelMask;
+}
+
+
+int AudioEngine::GetOutputSampleRate() const noexcept
+{
+ return static_cast(pImpl->masterRate);
+}
+
+
+unsigned int AudioEngine::GetOutputChannels() const noexcept
+{
+ return pImpl->masterChannels;
+}
+
+
+bool AudioEngine::IsAudioDevicePresent() const noexcept
+{
+ return pImpl->xaudio2 && !pImpl->mCriticalError;
+}
+
+
+bool AudioEngine::IsCriticalError() const noexcept
+{
+ return pImpl->mCriticalError;
+}
+
+
+// Voice management.
+void AudioEngine::SetDefaultSampleRate(int sampleRate)
+{
+ if ((sampleRate < XAUDIO2_MIN_SAMPLE_RATE) || (sampleRate > XAUDIO2_MAX_SAMPLE_RATE))
+ throw std::out_of_range("Default sample rate is out of range");
+
+ pImpl->defaultRate = sampleRate;
+}
+
+
+void AudioEngine::SetMaxVoicePool(size_t maxOneShots, size_t maxInstances)
+{
+ if (maxOneShots > 0)
+ pImpl->maxVoiceOneshots = maxOneShots;
+
+ if (maxInstances > 0)
+ pImpl->maxVoiceInstances = maxInstances;
+}
+
+
+void AudioEngine::TrimVoicePool()
+{
+ pImpl->TrimVoicePool();
+}
+
+
+_Use_decl_annotations_
+void AudioEngine::AllocateVoice(
+ const WAVEFORMATEX* wfx,
+ SOUND_EFFECT_INSTANCE_FLAGS flags,
+ bool oneshot,
+ IXAudio2SourceVoice** voice)
+{
+ pImpl->AllocateVoice(wfx, flags, oneshot, voice);
+}
+
+
+void AudioEngine::DestroyVoice(_In_ IXAudio2SourceVoice* voice) noexcept
+{
+ pImpl->DestroyVoice(voice);
+}
+
+
+void AudioEngine::RegisterNotify(_In_ IVoiceNotify* notify, bool usesUpdate)
+{
+ pImpl->RegisterNotify(notify, usesUpdate);
+}
+
+
+void AudioEngine::UnregisterNotify(_In_ IVoiceNotify* notify, bool oneshots, bool usesUpdate)
+{
+ pImpl->UnregisterNotify(notify, oneshots, usesUpdate);
+}
+
+
+IXAudio2* AudioEngine::GetInterface() const noexcept
+{
+ return pImpl->xaudio2.Get();
+}
+
+
+IXAudio2MasteringVoice* AudioEngine::GetMasterVoice() const noexcept
+{
+ return pImpl->mMasterVoice;
+}
+
+
+IXAudio2SubmixVoice* AudioEngine::GetReverbVoice() const noexcept
+{
+ return pImpl->mReverbVoice;
+}
+
+
+X3DAUDIO_HANDLE& AudioEngine::Get3DHandle() const noexcept
+{
+ return pImpl->mX3DAudio;
+}
+
+
+// Static methods.
+#if (defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)) || defined(USING_XAUDIO2_8)
+//--- Use Windows Runtime device enumeration ---
+
+// Note that this form of enumeration would also be needed for XAudio2.9 prior to Windows 10 (18362).
+//
+// If you care about supporting Windows 10 (17763), Windows Server 2019, or earlier Windows 10 builds,
+// you will need to modify the library to use this codepath for Windows desktop
+// -or- use XAudio2Redist -or- use XAudio 2.8.
+
+#pragma comment(lib,"runtimeobject.lib")
+#pragma warning(push)
+#pragma warning(disable: 4471 5204 5256)
+#ifdef __clang__
+#pragma clang diagnostic ignored "-Wnonportable-system-include-path"
+#endif
+#include
+#include
+#pragma warning(pop)
+
+#include
+
+#ifdef __clang__
+#pragma clang diagnostic ignored "-Wdeprecated-dynamic-exception-spec"
+#endif
+
+namespace
+{
+ const wchar_t* c_PKEY_AudioEngine_DeviceFormat = L"{f19f064d-082c-4e27-bc73-6882a1bb8e4c} 0";
+
+ class PropertyIterator : public Microsoft::WRL::RuntimeClass>
+ {
+ #if !defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)
+ InspectableClass(L"AudioEngine.PropertyIterator", FullTrust)
+ #else
+ InspectableClass(L"AudioEngine.PropertyIterator", BaseTrust)
+ #endif
+
+ public:
+ PropertyIterator() : mFirst(true), mString(c_PKEY_AudioEngine_DeviceFormat) {}
+
+ HRESULT STDMETHODCALLTYPE get_Current(HSTRING *current) override
+ {
+ if (!current)
+ return E_INVALIDARG;
+
+ if (mFirst)
+ {
+ *current = mString.Get();
+ }
+
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE get_HasCurrent(boolean *hasCurrent) override
+ {
+ if (!hasCurrent)
+ return E_INVALIDARG;
+
+ *hasCurrent = (mFirst) ? TRUE : FALSE;
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE MoveNext(boolean *hasCurrent) override
+ {
+ if (!hasCurrent)
+ return E_INVALIDARG;
+
+ *hasCurrent = FALSE;
+ mFirst = false;
+ return S_OK;
+ }
+
+ private:
+ bool mFirst;
+ Microsoft::WRL::Wrappers::HStringReference mString;
+
+ ~PropertyIterator() override = default;
+ };
+
+ class PropertyList : public Microsoft::WRL::RuntimeClass>
+ {
+ #if !defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)
+ InspectableClass(L"AudioEngine.PropertyList", FullTrust)
+ #else
+ InspectableClass(L"AudioEngine.PropertyList", BaseTrust)
+ #endif
+
+ public:
+ HRESULT STDMETHODCALLTYPE First(ABI::Windows::Foundation::Collections::IIterator **first) override
+ {
+ if (!first)
+ return E_INVALIDARG;
+
+ ComPtr p = Microsoft::WRL::Make();
+ *first = p.Detach();
+ return S_OK;
+ }
+
+ private:
+ ~PropertyList() override = default;
+ };
+
+ void GetDeviceOutputFormat(const wchar_t* deviceId, WAVEFORMATEX& wfx)
+ {
+ using namespace Microsoft::WRL;
+ using namespace Microsoft::WRL::Wrappers;
+ using namespace ABI::Windows::Foundation;
+ using namespace ABI::Windows::Foundation::Collections;
+ using namespace ABI::Windows::Devices::Enumeration;
+
+ #if !defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)
+ RoInitializeWrapper initialize(RO_INIT_MULTITHREADED);
+ ThrowIfFailed(initialize);
+ #endif
+
+ ComPtr diFactory;
+ HRESULT hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_Devices_Enumeration_DeviceInformation).Get(), &diFactory);
+ ThrowIfFailed(hr);
+
+ HString id;
+ if (!deviceId)
+ {
+ using namespace ABI::Windows::Media::Devices;
+
+ ComPtr mdStatics;
+ hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_Media_Devices_MediaDevice).Get(), &mdStatics);
+ ThrowIfFailed(hr);
+
+ hr = mdStatics->GetDefaultAudioRenderId(AudioDeviceRole_Default, id.GetAddressOf());
+ ThrowIfFailed(hr);
+ }
+ else
+ {
+ id.Set(deviceId);
+ }
+
+ ComPtr> operation;
+ ComPtr> props = Make();
+
+ hr = diFactory->CreateFromIdAsyncAdditionalProperties(id.Get(), props.Get(), operation.GetAddressOf());
+ if (FAILED(hr))
+ return;
+
+ ComPtr asyncinfo;
+ hr = operation.As(&asyncinfo);
+ ThrowIfFailed(hr);
+
+ AsyncStatus status;
+ hr = asyncinfo->get_Status(&status);
+ ThrowIfFailed(hr);
+
+ while (status == ABI::Windows::Foundation::AsyncStatus::Started)
+ {
+ Sleep(100);
+ hr = asyncinfo->get_Status(&status);
+ ThrowIfFailed(hr);
+ }
+
+ if (status != ABI::Windows::Foundation::AsyncStatus::Completed)
+ {
+ throw std::runtime_error("CreateFromIdAsync");
+ }
+
+ ComPtr devInfo;
+ hr = operation->GetResults(devInfo.GetAddressOf());
+ ThrowIfFailed(hr);
+
+ ComPtr> map;
+ hr = devInfo->get_Properties(map.GetAddressOf());
+ ThrowIfFailed(hr);
+
+ ComPtr value;
+ hr = map->Lookup(HStringReference(c_PKEY_AudioEngine_DeviceFormat).Get(), value.GetAddressOf());
+ if (SUCCEEDED(hr))
+ {
+ ComPtr pvalue;
+ if (SUCCEEDED(value.As(&pvalue)))
+ {
+ PropertyType ptype;
+ ThrowIfFailed(pvalue->get_Type(&ptype));
+
+ if (ptype == PropertyType_UInt8Array)
+ {
+ UINT32 length = 0;
+ BYTE* ptr;
+ ThrowIfFailed(pvalue->GetUInt8Array(&length, &ptr));
+
+ if (length >= sizeof(WAVEFORMATEX))
+ {
+ auto devicefx = reinterpret_cast(ptr);
+ memcpy(&wfx, devicefx, sizeof(WAVEFORMATEX));
+ wfx.wFormatTag = static_cast(GetFormatTag(devicefx));
+ }
+ }
+ }
+ }
+ }
+}
+
+std::vector AudioEngine::GetRendererDetails()
+{
+ std::vector list;
+
+ // Enumerating with WinRT using WRL (Win32 desktop app for Windows 8.x)
+ using namespace Microsoft::WRL::Wrappers;
+ using namespace ABI::Windows::Foundation;
+ using namespace ABI::Windows::Foundation::Collections;
+ using namespace ABI::Windows::Devices::Enumeration;
+
+#if !defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)
+ RoInitializeWrapper initialize(RO_INIT_MULTITHREADED);
+ ThrowIfFailed(initialize);
+#endif
+
+ ComPtr diFactory;
+ HRESULT hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_Devices_Enumeration_DeviceInformation).Get(), &diFactory);
+ ThrowIfFailed(hr);
+
+ ComPtr> operation;
+ hr = diFactory->FindAllAsyncDeviceClass(DeviceClass_AudioRender, operation.GetAddressOf());
+ ThrowIfFailed(hr);
+
+ ComPtr asyncinfo;
+ hr = operation.As(&asyncinfo);
+ ThrowIfFailed(hr);
+
+ AsyncStatus status;
+ hr = asyncinfo->get_Status(&status);
+ ThrowIfFailed(hr);
+
+ while (status == ABI::Windows::Foundation::AsyncStatus::Started)
+ {
+ Sleep(100);
+ hr = asyncinfo->get_Status(&status);
+ ThrowIfFailed(hr);
+ }
+
+ if (status != ABI::Windows::Foundation::AsyncStatus::Completed)
+ {
+ throw std::runtime_error("FindAllAsyncDeviceClass");
+ }
+
+ ComPtr> devices;
+ hr = operation->GetResults(devices.GetAddressOf());
+ ThrowIfFailed(hr);
+
+ unsigned int count = 0;
+ hr = devices->get_Size(&count);
+ ThrowIfFailed(hr);
+
+ if (!count)
+ return list;
+
+ for (unsigned int j = 0; j < count; ++j)
+ {
+ ComPtr deviceInfo;
+ hr = devices->GetAt(j, deviceInfo.GetAddressOf());
+ if (SUCCEEDED(hr))
+ {
+ RendererDetail device;
+
+ HString id;
+ if (SUCCEEDED(deviceInfo->get_Id(id.GetAddressOf())))
+ {
+ device.deviceId = id.GetRawBuffer(nullptr);
+ }
+
+ HString name;
+ if (SUCCEEDED(deviceInfo->get_Name(name.GetAddressOf())))
+ {
+ device.description = name.GetRawBuffer(nullptr);
+ }
+
+ list.emplace_back(device);
+ }
+ }
+
+ return list;
+}
+
+
+#elif defined(_XBOX_ONE)
+//--- Use legacy Xbox One XDK device enumeration ---
+
+#include
+#include
+
+namespace
+{
+ void GetDeviceOutputFormat(const wchar_t*, WAVEFORMATEX& wfx)
+ {
+ wfx.nSamplesPerSec = 48000;
+ wfx.wBitsPerSample = 24;
+ }
+}
+
+std::vector AudioEngine::GetRendererDetails()
+{
+ std::vector list;
+
+ using namespace Microsoft::WRL;
+ using namespace Microsoft::WRL::Wrappers;
+ using namespace ABI::Windows::Foundation;
+ using namespace ABI::Windows::Media::Devices;
+
+ ComPtr mdStatics;
+ HRESULT hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_Media_Devices_MediaDevice).Get(), &mdStatics);
+ ThrowIfFailed(hr);
+
+ HString id;
+ hr = mdStatics->GetDefaultAudioRenderId(AudioDeviceRole_Default, id.GetAddressOf());
+ ThrowIfFailed(hr);
+
+ RendererDetail device;
+ device.deviceId = id.GetRawBuffer(nullptr);
+ device.description = L"Default";
+ list.emplace_back(device);
+
+ return list;
+}
+
+
+#elif defined(USING_XAUDIO2_9) || defined(USING_XAUDIO2_REDIST) || defined(_GAMING_DESKTOP)
+#include
+//--- Use WASAPI device enumeration ---
+
+namespace
+{
+ void GetDeviceOutputFormat(const wchar_t* deviceId, WAVEFORMATEX& wfx)
+ {
+ ComPtr devEnum;
+ if (FAILED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(devEnum.GetAddressOf()))))
+ return;
+
+ ComPtr endpoint;
+ if (!deviceId)
+ {
+ if (FAILED(devEnum->GetDefaultAudioEndpoint(eRender, eConsole, endpoint.GetAddressOf())))
+ return;
+ }
+ else
+ {
+ if (FAILED(devEnum->GetDevice(deviceId, endpoint.GetAddressOf())))
+ return;
+ }
+
+ // Value matches Windows SDK header um\mmdeviceapi.h
+ constexpr static PROPERTYKEY s_PKEY_AudioEngine_DeviceFormat = { { 0xf19f064d, 0x82c, 0x4e27, { 0xbc, 0x73, 0x68, 0x82, 0xa1, 0xbb, 0x8e, 0x4c } }, 0 };
+
+ ComPtr props;
+ if (SUCCEEDED(endpoint->OpenPropertyStore(STGM_READ, props.GetAddressOf())))
+ {
+ PROPVARIANT var;
+ PropVariantInit(&var);
+
+ if (SUCCEEDED(props->GetValue(s_PKEY_AudioEngine_DeviceFormat, &var)))
+ {
+ if (var.vt == VT_BLOB && var.blob.cbSize >= sizeof(WAVEFORMATEX))
+ {
+ auto devicefx = reinterpret_cast(var.blob.pBlobData);
+ memcpy(&wfx, devicefx, sizeof(WAVEFORMATEX));
+ wfx.wFormatTag = static_cast(GetFormatTag(devicefx));
+ }
+ PropVariantClear(&var);
+ }
+ }
+ }
+}
+
+std::vector AudioEngine::GetRendererDetails()
+{
+ std::vector list;
+
+ // Value matches Windows SDK header shared\devpkey.h
+ constexpr static PROPERTYKEY s_PKEY_Device_FriendlyName = { { 0xa45c254e, 0xdf1c, 0x4efd, { 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0 } }, 14 };
+
+ ComPtr devEnum;
+ HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(devEnum.GetAddressOf()));
+ ThrowIfFailed(hr);
+
+ ComPtr devices;
+ hr = devEnum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices);
+ ThrowIfFailed(hr);
+
+ UINT count = 0;
+ ThrowIfFailed(devices->GetCount(&count));
+
+ if (!count)
+ return list;
+
+ for (UINT j = 0; j < count; ++j)
+ {
+ ComPtr endpoint;
+ hr = devices->Item(j, endpoint.GetAddressOf());
+ ThrowIfFailed(hr);
+
+ LPWSTR id = nullptr;
+ ThrowIfFailed(endpoint->GetId(&id));
+
+ RendererDetail device;
+ device.deviceId = id;
+ CoTaskMemFree(id);
+
+ ComPtr props;
+ if (SUCCEEDED(endpoint->OpenPropertyStore(STGM_READ, props.GetAddressOf())))
+ {
+ PROPVARIANT var;
+ PropVariantInit(&var);
+
+ if (SUCCEEDED(props->GetValue(s_PKEY_Device_FriendlyName, &var)))
+ {
+ if (var.vt == VT_LPWSTR)
+ {
+ device.description = var.pwszVal;
+ }
+ PropVariantClear(&var);
+ }
+ }
+
+ list.emplace_back(device);
+ }
+
+ return list;
+}
+
+
+#else
+#error DirectX Tool Kit for Audio not supported on this platform
+#endif
+
+
+//--------------------------------------------------------------------------------------
+// Adapters for /Zc:wchar_t- clients
+#if defined(_MSC_VER) && !defined(_NATIVE_WCHAR_T_DEFINED)
+
+_Use_decl_annotations_
+AudioEngine::AudioEngine(
+ AUDIO_ENGINE_FLAGS flags,
+ const WAVEFORMATEX* wfx,
+ const __wchar_t* deviceId,
+ AUDIO_STREAM_CATEGORY category) noexcept(false) :
+ AudioEngine(flags, wfx, reinterpret_cast(deviceId), category)
+{
+}
+
+_Use_decl_annotations_
+bool AudioEngine::Reset(const WAVEFORMATEX* wfx, const __wchar_t* deviceId)
+{
+ return Reset(wfx, reinterpret_cast(deviceId));
+}
+
+#endif // !_NATIVE_WCHAR_T_DEFINED
diff --git a/Common/DirectXTK12/Audio/DynamicSoundEffectInstance.cpp b/Common/DirectXTK12/Audio/DynamicSoundEffectInstance.cpp
new file mode 100644
index 0000000..473e950
--- /dev/null
+++ b/Common/DirectXTK12/Audio/DynamicSoundEffectInstance.cpp
@@ -0,0 +1,377 @@
+//--------------------------------------------------------------------------------------
+// File: DynamicSoundEffectInstance.cpp
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+// http://go.microsoft.com/fwlink/?LinkId=248929
+// http://go.microsoft.com/fwlink/?LinkID=615561
+//--------------------------------------------------------------------------------------
+
+#include "pch.h"
+#include "SoundCommon.h"
+
+using namespace DirectX;
+
+
+//======================================================================================
+// DynamicSoundEffectInstance
+//======================================================================================
+
+// Internal object implementation class.
+class DynamicSoundEffectInstance::Impl : public IVoiceNotify
+{
+public:
+ Impl(_In_ AudioEngine* engine,
+ _In_ DynamicSoundEffectInstance* object,
+ std::function& bufferNeeded,
+ int sampleRate, int channels, int sampleBits,
+ SOUND_EFFECT_INSTANCE_FLAGS flags) :
+ mBase(),
+ mBufferNeeded(nullptr),
+ mObject(object)
+ {
+ if ((sampleRate < XAUDIO2_MIN_SAMPLE_RATE)
+ || (sampleRate > XAUDIO2_MAX_SAMPLE_RATE))
+ {
+ DebugTrace("DynamicSoundEffectInstance sampleRate must be in range %u...%u\n", XAUDIO2_MIN_SAMPLE_RATE, XAUDIO2_MAX_SAMPLE_RATE);
+ throw std::out_of_range("DynamicSoundEffectInstance");
+ }
+
+ if (!channels || (channels > 8))
+ {
+ DebugTrace("DynamicSoundEffectInstance channels must be in range 1...8\n");
+ throw std::out_of_range("DynamicSoundEffectInstance channel count out of range");
+ }
+
+ switch (sampleBits)
+ {
+ case 8:
+ case 16:
+ break;
+
+ default:
+ DebugTrace("DynamicSoundEffectInstance sampleBits must be 8-bit or 16-bit\n");
+ throw std::invalid_argument("DynamicSoundEffectInstance supports 8 or 16 bit");
+ }
+
+ mBufferEvent.reset(CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE));
+ if (!mBufferEvent)
+ {
+ throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "CreateEventEx");
+ }
+
+ CreateIntegerPCM(&mWaveFormat, sampleRate, channels, sampleBits);
+
+ assert(engine != nullptr);
+ engine->RegisterNotify(this, true);
+
+ mBase.Initialize(engine, &mWaveFormat, flags);
+
+ mBufferNeeded = bufferNeeded;
+ }
+
+ Impl(Impl&&) = default;
+ Impl& operator= (Impl&&) = default;
+
+ Impl(Impl const&) = delete;
+ Impl& operator= (Impl const&) = delete;
+
+ ~Impl() override
+ {
+ mBase.DestroyVoice();
+
+ if (mBase.engine)
+ {
+ mBase.engine->UnregisterNotify(this, false, true);
+ mBase.engine = nullptr;
+ }
+ }
+
+ void Play();
+
+ void Resume();
+
+ void SubmitBuffer(_In_reads_bytes_(audioBytes) const uint8_t* pAudioData, uint32_t offset, size_t audioBytes);
+
+ const WAVEFORMATEX* GetFormat() const noexcept { return &mWaveFormat; }
+
+ // IVoiceNotify
+ void __cdecl OnBufferEnd() override
+ {
+ SetEvent(mBufferEvent.get());
+ }
+
+ void __cdecl OnCriticalError() override
+ {
+ mBase.OnCriticalError();
+ }
+
+ void __cdecl OnReset() override
+ {
+ mBase.OnReset();
+ }
+
+ void __cdecl OnUpdate() override;
+
+ void __cdecl OnDestroyEngine() noexcept override
+ {
+ mBase.OnDestroy();
+ }
+
+ void __cdecl OnTrim() override
+ {
+ mBase.OnTrim();
+ }
+
+ void __cdecl GatherStatistics(AudioStatistics& stats) const noexcept override
+ {
+ mBase.GatherStatistics(stats);
+ }
+
+ void __cdecl OnDestroyParent() noexcept override
+ {
+ }
+
+ SoundEffectInstanceBase mBase;
+
+private:
+ ScopedHandle mBufferEvent;
+ std::function mBufferNeeded;
+ DynamicSoundEffectInstance* mObject;
+ WAVEFORMATEX mWaveFormat;
+};
+
+
+void DynamicSoundEffectInstance::Impl::Play()
+{
+ if (!mBase.voice)
+ {
+ mBase.AllocateVoice(&mWaveFormat);
+ }
+
+ std::ignore = mBase.Play();
+
+ if (mBase.voice && (mBase.state == PLAYING) && (mBase.GetPendingBufferCount() <= 2))
+ {
+ SetEvent(mBufferEvent.get());
+ }
+}
+
+
+void DynamicSoundEffectInstance::Impl::Resume()
+{
+ if (mBase.voice && (mBase.state == PAUSED))
+ {
+ mBase.Resume();
+
+ if ((mBase.state == PLAYING) && (mBase.GetPendingBufferCount() <= 2))
+ {
+ SetEvent(mBufferEvent.get());
+ }
+ }
+}
+
+
+_Use_decl_annotations_
+void DynamicSoundEffectInstance::Impl::SubmitBuffer(const uint8_t* pAudioData, uint32_t offset, size_t audioBytes)
+{
+ if (!pAudioData || !audioBytes)
+ throw std::invalid_argument("Invalid audio data buffer");
+
+ if (audioBytes > UINT32_MAX)
+ throw std::out_of_range("SubmitBuffer");
+
+ XAUDIO2_BUFFER buffer = {};
+ buffer.AudioBytes = static_cast(audioBytes);
+ buffer.pAudioData = pAudioData;
+
+ if (offset)
+ {
+ assert(mWaveFormat.wFormatTag == WAVE_FORMAT_PCM);
+ buffer.PlayBegin = offset / mWaveFormat.nBlockAlign;
+ buffer.PlayLength = static_cast((audioBytes - offset) / mWaveFormat.nBlockAlign);
+ }
+
+ buffer.pContext = this;
+
+ HRESULT hr = mBase.voice->SubmitSourceBuffer(&buffer, nullptr);
+ if (FAILED(hr))
+ {
+ #ifdef _DEBUG
+ DebugTrace("ERROR: DynamicSoundEffectInstance failed (%08X) when submitting buffer:\n", static_cast(hr));
+
+ DebugTrace("\tFormat Tag %u, %u channels, %u-bit, %u Hz, %zu bytes [%u offset)\n",
+ mWaveFormat.wFormatTag, mWaveFormat.nChannels, mWaveFormat.wBitsPerSample, mWaveFormat.nSamplesPerSec, audioBytes, offset);
+ #endif
+ throw std::runtime_error("SubmitSourceBuffer");
+ }
+}
+
+
+void DynamicSoundEffectInstance::Impl::OnUpdate()
+{
+ const DWORD result = WaitForSingleObjectEx(mBufferEvent.get(), 0, FALSE);
+ switch (result)
+ {
+ case WAIT_TIMEOUT:
+ break;
+
+ case WAIT_OBJECT_0:
+ if (mBufferNeeded)
+ {
+ // This callback happens on the same thread that called AudioEngine::Update()
+ mBufferNeeded(mObject);
+ }
+ break;
+
+ case WAIT_FAILED:
+ throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "WaitForSingleObjectEx");
+ }
+}
+
+
+
+//--------------------------------------------------------------------------------------
+// DynamicSoundEffectInstance
+//--------------------------------------------------------------------------------------
+
+#pragma warning( disable : 4355 )
+
+// Public constructors
+_Use_decl_annotations_
+DynamicSoundEffectInstance::DynamicSoundEffectInstance(
+ AudioEngine* engine,
+ std::function bufferNeeded,
+ int sampleRate,
+ int channels,
+ int sampleBits,
+ SOUND_EFFECT_INSTANCE_FLAGS flags) :
+ pImpl(std::make_unique(engine, this, bufferNeeded, sampleRate, channels, sampleBits, flags))
+{
+}
+
+
+DynamicSoundEffectInstance::DynamicSoundEffectInstance(DynamicSoundEffectInstance&&) noexcept = default;
+DynamicSoundEffectInstance& DynamicSoundEffectInstance::operator= (DynamicSoundEffectInstance&&) noexcept = default;
+DynamicSoundEffectInstance::~DynamicSoundEffectInstance() = default;
+
+
+// Public methods.
+void DynamicSoundEffectInstance::Play()
+{
+ pImpl->Play();
+}
+
+
+void DynamicSoundEffectInstance::Stop(bool immediate) noexcept
+{
+ bool looped = false;
+ pImpl->mBase.Stop(immediate, looped);
+}
+
+
+void DynamicSoundEffectInstance::Pause() noexcept
+{
+ pImpl->mBase.Pause();
+}
+
+
+void DynamicSoundEffectInstance::Resume()
+{
+ pImpl->Resume();
+}
+
+
+void DynamicSoundEffectInstance::SetVolume(float volume)
+{
+ pImpl->mBase.SetVolume(volume);
+}
+
+
+void DynamicSoundEffectInstance::SetPitch(float pitch)
+{
+ pImpl->mBase.SetPitch(pitch);
+}
+
+
+void DynamicSoundEffectInstance::SetPan(float pan)
+{
+ pImpl->mBase.SetPan(pan);
+}
+
+
+void DynamicSoundEffectInstance::Apply3D(const X3DAUDIO_LISTENER& listener, const X3DAUDIO_EMITTER& emitter, bool rhcoords)
+{
+ pImpl->mBase.Apply3D(listener, emitter, rhcoords);
+}
+
+
+_Use_decl_annotations_
+void DynamicSoundEffectInstance::SubmitBuffer(const uint8_t* pAudioData, size_t audioBytes)
+{
+ pImpl->SubmitBuffer(pAudioData, 0, audioBytes);
+}
+
+
+_Use_decl_annotations_
+void DynamicSoundEffectInstance::SubmitBuffer(const uint8_t* pAudioData, uint32_t offset, size_t audioBytes)
+{
+ pImpl->SubmitBuffer(pAudioData, offset, audioBytes);
+}
+
+
+// Public accessors.
+SoundState DynamicSoundEffectInstance::GetState() noexcept
+{
+ return pImpl->mBase.GetState(false);
+}
+
+
+size_t DynamicSoundEffectInstance::GetSampleDuration(size_t bytes) const noexcept
+{
+ auto wfx = pImpl->GetFormat();
+ if (!wfx || !wfx->wBitsPerSample || !wfx->nChannels)
+ return 0;
+
+ return static_cast((uint64_t(bytes) * 8)
+ / (uint64_t(wfx->wBitsPerSample) * uint64_t(wfx->nChannels)));
+}
+
+
+size_t DynamicSoundEffectInstance::GetSampleDurationMS(size_t bytes) const noexcept
+{
+ auto wfx = pImpl->GetFormat();
+ if (!wfx || !wfx->nAvgBytesPerSec)
+ return 0;
+
+ return static_cast((uint64_t(bytes) * 1000) / wfx->nAvgBytesPerSec);
+}
+
+
+size_t DynamicSoundEffectInstance::GetSampleSizeInBytes(uint64_t duration) const noexcept
+{
+ auto wfx = pImpl->GetFormat();
+ if (!wfx || !wfx->nSamplesPerSec)
+ return 0;
+
+ return static_cast(((duration * wfx->nSamplesPerSec) / 1000) * wfx->nBlockAlign);
+}
+
+
+int DynamicSoundEffectInstance::GetPendingBufferCount() const noexcept
+{
+ return pImpl->mBase.GetPendingBufferCount();
+}
+
+
+unsigned int DynamicSoundEffectInstance::GetChannelCount() const noexcept
+{
+ return pImpl->mBase.GetChannelCount();
+}
+
+
+const WAVEFORMATEX* DynamicSoundEffectInstance::GetFormat() const noexcept
+{
+ return pImpl->GetFormat();
+}
diff --git a/Common/DirectXTK12/Audio/SoundCommon.cpp b/Common/DirectXTK12/Audio/SoundCommon.cpp
new file mode 100644
index 0000000..9b59a36
--- /dev/null
+++ b/Common/DirectXTK12/Audio/SoundCommon.cpp
@@ -0,0 +1,982 @@
+//--------------------------------------------------------------------------------------
+// File: SoundCommon.cpp
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+// http://go.microsoft.com/fwlink/?LinkId=248929
+// http://go.microsoft.com/fwlink/?LinkID=615561
+//--------------------------------------------------------------------------------------
+
+#include "pch.h"
+#include "SoundCommon.h"
+
+using namespace DirectX;
+
+
+namespace
+{
+ template WORD ChannelsSpecifiedInMask(T x) noexcept
+ {
+ WORD bitCount = 0;
+ while (x) { ++bitCount; x &= (x - 1); }
+ return bitCount;
+ }
+
+ constexpr int MSADPCM_HEADER_LENGTH = 7;
+
+ constexpr uint16_t MSADPCM_FORMAT_EXTRA_BYTES = 32;
+
+ constexpr uint16_t MSADPCM_BITS_PER_SAMPLE = 4;
+ constexpr uint16_t MSADPCM_NUM_COEFFICIENTS = 7;
+
+ constexpr uint16_t MSADPCM_MIN_SAMPLES_PER_BLOCK = 4;
+ constexpr uint16_t MSADPCM_MAX_SAMPLES_PER_BLOCK = 64000;
+}
+
+
+//======================================================================================
+// Wave format utilities
+//======================================================================================
+
+bool DirectX::IsValid(_In_ const WAVEFORMATEX* wfx) noexcept
+{
+ if (!wfx)
+ return false;
+
+ if (!wfx->nChannels)
+ {
+ DebugTrace("ERROR: Wave format must have at least 1 channel\n");
+ return false;
+ }
+
+ if (wfx->nChannels > XAUDIO2_MAX_AUDIO_CHANNELS)
+ {
+ DebugTrace("ERROR: Wave format must have less than %u channels (%u)\n", XAUDIO2_MAX_AUDIO_CHANNELS, wfx->nChannels);
+ return false;
+ }
+
+ if (!wfx->nSamplesPerSec)
+ {
+ DebugTrace("ERROR: Wave format cannot have a sample rate of 0\n");
+ return false;
+ }
+
+ if ((wfx->nSamplesPerSec < XAUDIO2_MIN_SAMPLE_RATE)
+ || (wfx->nSamplesPerSec > XAUDIO2_MAX_SAMPLE_RATE))
+ {
+ DebugTrace("ERROR: Wave format channel count must be in range %u..%u (%u)\n",
+ XAUDIO2_MIN_SAMPLE_RATE, XAUDIO2_MAX_SAMPLE_RATE, wfx->nSamplesPerSec);
+ return false;
+ }
+
+ switch (wfx->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+
+ switch (wfx->wBitsPerSample)
+ {
+ case 8:
+ case 16:
+ case 24:
+ case 32:
+ break;
+
+ default:
+ DebugTrace("ERROR: Wave format integer PCM must have 8, 16, 24, or 32 bits per sample (%u)\n", wfx->wBitsPerSample);
+ return false;
+ }
+
+ if (wfx->nBlockAlign != (wfx->nChannels * wfx->wBitsPerSample / 8))
+ {
+ DebugTrace("ERROR: Wave format integer PCM - nBlockAlign (%u) != nChannels (%u) * wBitsPerSample (%u) / 8\n",
+ wfx->nBlockAlign, wfx->nChannels, wfx->wBitsPerSample);
+ return false;
+ }
+
+ if (wfx->nAvgBytesPerSec != (wfx->nSamplesPerSec * wfx->nBlockAlign))
+ {
+ DebugTrace("ERROR: Wave format integer PCM - nAvgBytesPerSec (%lu) != nSamplesPerSec (%lu) * nBlockAlign (%u)\n",
+ wfx->nAvgBytesPerSec, wfx->nSamplesPerSec, wfx->nBlockAlign);
+ return false;
+ }
+
+ return true;
+
+ case WAVE_FORMAT_IEEE_FLOAT:
+
+ if (wfx->wBitsPerSample != 32)
+ {
+ DebugTrace("ERROR: Wave format float PCM must have 32-bits per sample (%u)\n", wfx->wBitsPerSample);
+ return false;
+ }
+
+ if (wfx->nBlockAlign != (wfx->nChannels * wfx->wBitsPerSample / 8))
+ {
+ DebugTrace("ERROR: Wave format float PCM - nBlockAlign (%u) != nChannels (%u) * wBitsPerSample (%u) / 8\n",
+ wfx->nBlockAlign, wfx->nChannels, wfx->wBitsPerSample);
+ return false;
+ }
+
+ if (wfx->nAvgBytesPerSec != (wfx->nSamplesPerSec * wfx->nBlockAlign))
+ {
+ DebugTrace("ERROR: Wave format float PCM - nAvgBytesPerSec (%lu) != nSamplesPerSec (%lu) * nBlockAlign (%u)\n",
+ wfx->nAvgBytesPerSec, wfx->nSamplesPerSec, wfx->nBlockAlign);
+ return false;
+ }
+
+ return true;
+
+ case WAVE_FORMAT_ADPCM:
+
+ if ((wfx->nChannels != 1) && (wfx->nChannels != 2))
+ {
+ DebugTrace("ERROR: Wave format ADPCM must have 1 or 2 channels (%u)\n", wfx->nChannels);
+ return false;
+ }
+
+ if (wfx->wBitsPerSample != MSADPCM_BITS_PER_SAMPLE)
+ {
+ DebugTrace("ERROR: Wave format ADPCM must have 4 bits per sample (%u)\n", wfx->wBitsPerSample);
+ return false;
+ }
+
+ if (wfx->cbSize != MSADPCM_FORMAT_EXTRA_BYTES)
+ {
+ DebugTrace("ERROR: Wave format ADPCM must have cbSize = 32 (%u)\n", wfx->cbSize);
+ return false;
+ }
+ else
+ {
+ auto wfadpcm = reinterpret_cast(wfx);
+
+ if (wfadpcm->wNumCoef != MSADPCM_NUM_COEFFICIENTS)
+ {
+ DebugTrace("ERROR: Wave format ADPCM must have 7 coefficients (%u)\n", wfadpcm->wNumCoef);
+ return false;
+ }
+
+ bool valid = true;
+ for (size_t j = 0; j < MSADPCM_NUM_COEFFICIENTS; ++j)
+ {
+ // Microsoft ADPCM standard encoding coefficients
+ static const short g_pAdpcmCoefficients1[] = { 256, 512, 0, 192, 240, 460, 392 };
+ static const short g_pAdpcmCoefficients2[] = { 0, -256, 0, 64, 0, -208, -232 };
+
+ if (wfadpcm->aCoef[j].iCoef1 != g_pAdpcmCoefficients1[j]
+ || wfadpcm->aCoef[j].iCoef2 != g_pAdpcmCoefficients2[j])
+ {
+ valid = false;
+ }
+ }
+
+ if (!valid)
+ {
+ DebugTrace("ERROR: Wave formt ADPCM found non-standard coefficients\n");
+ return false;
+ }
+
+ if ((wfadpcm->wSamplesPerBlock < MSADPCM_MIN_SAMPLES_PER_BLOCK)
+ || (wfadpcm->wSamplesPerBlock > MSADPCM_MAX_SAMPLES_PER_BLOCK))
+ {
+ DebugTrace("ERROR: Wave format ADPCM wSamplesPerBlock must be 4..64000 (%u)\n", wfadpcm->wSamplesPerBlock);
+ return false;
+ }
+
+ if (wfadpcm->wfx.nChannels == 1 && (wfadpcm->wSamplesPerBlock % 2))
+ {
+ DebugTrace("ERROR: Wave format ADPCM mono files must have even wSamplesPerBlock\n");
+ return false;
+ }
+
+ const int nHeaderBytes = MSADPCM_HEADER_LENGTH * wfx->nChannels;
+ const int nBitsPerFrame = MSADPCM_BITS_PER_SAMPLE * wfx->nChannels;
+ const int nPcmFramesPerBlock = (wfx->nBlockAlign - nHeaderBytes) * 8 / nBitsPerFrame + 2;
+
+ if (wfadpcm->wSamplesPerBlock != nPcmFramesPerBlock)
+ {
+ DebugTrace("ERROR: Wave format ADPCM %u-channel with nBlockAlign = %u must have wSamplesPerBlock = %d (%u)\n",
+ wfx->nChannels, wfx->nBlockAlign, nPcmFramesPerBlock, wfadpcm->wSamplesPerBlock);
+ return false;
+ }
+ }
+ return true;
+
+ case WAVE_FORMAT_WMAUDIO2:
+ case WAVE_FORMAT_WMAUDIO3:
+
+ #ifdef DIRECTX_ENABLE_XWMA
+
+ if (wfx->wBitsPerSample != 16)
+ {
+ DebugTrace("ERROR: Wave format xWMA only supports 16-bit data\n");
+ return false;
+ }
+
+ if (!wfx->nBlockAlign)
+ {
+ DebugTrace("ERROR: Wave format xWMA must have a non-zero nBlockAlign\n");
+ return false;
+ }
+
+ if (!wfx->nAvgBytesPerSec)
+ {
+ DebugTrace("ERROR: Wave format xWMA must have a non-zero nAvgBytesPerSec\n");
+ return false;
+ }
+
+ return true;
+
+ #else
+ DebugTrace("ERROR: Wave format xWMA not supported by this version of DirectXTK for Audio\n");
+ return false;
+ #endif
+
+ case 0x166 /* WAVE_FORMAT_XMA2 */:
+
+ #ifdef DIRECTX_ENABLE_XMA2
+
+ static_assert(WAVE_FORMAT_XMA2 == 0x166, "Unrecognized XMA2 tag");
+
+ if (wfx->nBlockAlign != wfx->nChannels * XMA_OUTPUT_SAMPLE_BYTES)
+ {
+ DebugTrace("ERROR: Wave format XMA2 - nBlockAlign (%u) != nChannels(%u) * %u\n", wfx->nBlockAlign, wfx->nChannels, XMA_OUTPUT_SAMPLE_BYTES);
+ return false;
+ }
+
+ if (wfx->wBitsPerSample != XMA_OUTPUT_SAMPLE_BITS)
+ {
+ DebugTrace("ERROR: Wave format XMA2 wBitsPerSample (%u) should be %u\n", wfx->wBitsPerSample, XMA_OUTPUT_SAMPLE_BITS);
+ return false;
+ }
+
+ if (wfx->cbSize != (sizeof(XMA2WAVEFORMATEX) - sizeof(WAVEFORMATEX)))
+ {
+ DebugTrace("ERROR: Wave format XMA2 - cbSize must be %zu (%u)\n", (sizeof(XMA2WAVEFORMATEX) - sizeof(WAVEFORMATEX)), wfx->cbSize);
+ return false;
+ }
+ else
+ {
+ auto xmaFmt = reinterpret_cast(wfx);
+
+ if (xmaFmt->EncoderVersion < 3)
+ {
+ DebugTrace("ERROR: Wave format XMA2 encoder version (%u) - 3 or higher is required\n", xmaFmt->EncoderVersion);
+ return false;
+ }
+
+ if (!xmaFmt->BlockCount)
+ {
+ DebugTrace("ERROR: Wave format XMA2 BlockCount must be non-zero\n");
+ return false;
+ }
+
+ if (!xmaFmt->BytesPerBlock || (xmaFmt->BytesPerBlock > XMA_READBUFFER_MAX_BYTES))
+ {
+ DebugTrace("ERROR: Wave format XMA2 BytesPerBlock (%u) is invalid\n", xmaFmt->BytesPerBlock);
+ return false;
+ }
+
+ if (xmaFmt->ChannelMask)
+ {
+ auto channelBits = ChannelsSpecifiedInMask(xmaFmt->ChannelMask);
+ if (channelBits != wfx->nChannels)
+ {
+ DebugTrace("ERROR: Wave format XMA2 - nChannels=%u but ChannelMask (%08X) has %u bits set\n",
+ xmaFmt->ChannelMask, wfx->nChannels, channelBits);
+ return false;
+ }
+ }
+
+ if (xmaFmt->NumStreams != ((wfx->nChannels + 1) / 2))
+ {
+ DebugTrace("ERROR: Wave format XMA2 - NumStreams (%u) != ( nChannels(%u) + 1 ) / 2\n",
+ xmaFmt->NumStreams, wfx->nChannels);
+ return false;
+ }
+
+ if ((xmaFmt->PlayBegin + xmaFmt->PlayLength) > xmaFmt->SamplesEncoded)
+ {
+ DebugTrace("ERROR: Wave format XMA2 play region too large (%u + %u > %u)\n",
+ xmaFmt->PlayBegin, xmaFmt->PlayLength, xmaFmt->SamplesEncoded);
+ return false;
+ }
+
+ if ((xmaFmt->LoopBegin + xmaFmt->LoopLength) > xmaFmt->SamplesEncoded)
+ {
+ DebugTrace("ERROR: Wave format XMA2 loop region too large (%u + %u > %u)\n",
+ xmaFmt->LoopBegin, xmaFmt->LoopLength, xmaFmt->SamplesEncoded);
+ return false;
+ }
+ }
+ return true;
+
+ #else
+ DebugTrace("ERROR: Wave format XMA2 not supported by this version of DirectXTK for Audio\n");
+ return false;
+ #endif
+
+ case WAVE_FORMAT_EXTENSIBLE:
+ if (wfx->cbSize < (sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)))
+ {
+ DebugTrace("ERROR: Wave format WAVE_FORMAT_EXTENSIBLE - cbSize must be %zu (%u)\n",
+ (sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)), wfx->cbSize);
+ return false;
+ }
+ else
+ {
+ static const GUID s_wfexBase = { 0x00000000, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 } };
+
+ auto wfex = reinterpret_cast(wfx);
+
+ if (memcmp(reinterpret_cast(&wfex->SubFormat) + sizeof(DWORD),
+ reinterpret_cast(&s_wfexBase) + sizeof(DWORD), sizeof(GUID) - sizeof(DWORD)) != 0)
+ {
+ DebugTrace("ERROR: Wave format WAVEFORMATEXTENSIBLE encountered with unknown GUID ({%8.8lX-%4.4X-%4.4X-%2.2X%2.2X-%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X})\n",
+ wfex->SubFormat.Data1, wfex->SubFormat.Data2, wfex->SubFormat.Data3,
+ wfex->SubFormat.Data4[0], wfex->SubFormat.Data4[1], wfex->SubFormat.Data4[2], wfex->SubFormat.Data4[3],
+ wfex->SubFormat.Data4[4], wfex->SubFormat.Data4[5], wfex->SubFormat.Data4[6], wfex->SubFormat.Data4[7]);
+ return false;
+ }
+
+ switch (wfex->SubFormat.Data1)
+ {
+ case WAVE_FORMAT_PCM:
+
+ switch (wfx->wBitsPerSample)
+ {
+ case 8:
+ case 16:
+ case 24:
+ case 32:
+ break;
+
+ default:
+ DebugTrace("ERROR: Wave format integer PCM must have 8, 16, 24, or 32 bits per sample (%u)\n",
+ wfx->wBitsPerSample);
+ return false;
+ }
+
+ switch (wfex->Samples.wValidBitsPerSample)
+ {
+ case 0:
+ case 8:
+ case 16:
+ case 20:
+ case 24:
+ case 32:
+ break;
+
+ default:
+ DebugTrace("ERROR: Wave format integer PCM must have 8, 16, 20, 24, or 32 valid bits per sample (%u)\n",
+ wfex->Samples.wValidBitsPerSample);
+ return false;
+ }
+
+ if (wfex->Samples.wValidBitsPerSample
+ && (wfex->Samples.wValidBitsPerSample > wfx->wBitsPerSample))
+ {
+ DebugTrace("ERROR: Wave format ingter PCM wValidBitsPerSample (%u) is greater than wBitsPerSample (%u)\n",
+ wfex->Samples.wValidBitsPerSample, wfx->wBitsPerSample);
+ return false;
+ }
+
+ if (wfx->nBlockAlign != (wfx->nChannels * wfx->wBitsPerSample / 8))
+ {
+ DebugTrace("ERROR: Wave format integer PCM - nBlockAlign (%u) != nChannels (%u) * wBitsPerSample (%u) / 8\n",
+ wfx->nBlockAlign, wfx->nChannels, wfx->wBitsPerSample);
+ return false;
+ }
+
+ if (wfx->nAvgBytesPerSec != (wfx->nSamplesPerSec * wfx->nBlockAlign))
+ {
+ DebugTrace("ERROR: Wave format integer PCM - nAvgBytesPerSec (%lu) != nSamplesPerSec (%lu) * nBlockAlign (%u)\n",
+ wfx->nAvgBytesPerSec, wfx->nSamplesPerSec, wfx->nBlockAlign);
+ return false;
+ }
+
+ break;
+
+ case WAVE_FORMAT_IEEE_FLOAT:
+
+ if (wfx->wBitsPerSample != 32)
+ {
+ DebugTrace("ERROR: Wave format float PCM must have 32-bits per sample (%u)\n", wfx->wBitsPerSample);
+ return false;
+ }
+
+ switch (wfex->Samples.wValidBitsPerSample)
+ {
+ case 0:
+ case 32:
+ break;
+
+ default:
+ DebugTrace("ERROR: Wave format float PCM must have 32 valid bits per sample (%u)\n",
+ wfex->Samples.wValidBitsPerSample);
+ return false;
+ }
+
+ if (wfx->nBlockAlign != (wfx->nChannels * wfx->wBitsPerSample / 8))
+ {
+ DebugTrace("ERROR: Wave format float PCM - nBlockAlign (%u) != nChannels (%u) * wBitsPerSample (%u) / 8\n",
+ wfx->nBlockAlign, wfx->nChannels, wfx->wBitsPerSample);
+ return false;
+ }
+
+ if (wfx->nAvgBytesPerSec != (wfx->nSamplesPerSec * wfx->nBlockAlign))
+ {
+ DebugTrace("ERROR: Wave format float PCM - nAvgBytesPerSec (%lu) != nSamplesPerSec (%lu) * nBlockAlign (%u)\n",
+ wfx->nAvgBytesPerSec, wfx->nSamplesPerSec, wfx->nBlockAlign);
+ return false;
+ }
+
+ break;
+
+ case WAVE_FORMAT_ADPCM:
+ DebugTrace("ERROR: Wave format ADPCM is not supported as a WAVEFORMATEXTENSIBLE\n");
+ return false;
+
+ case WAVE_FORMAT_WMAUDIO2:
+ case WAVE_FORMAT_WMAUDIO3:
+
+ #ifdef DIRECTX_ENABLE_XWMA
+
+ if (wfx->wBitsPerSample != 16)
+ {
+ DebugTrace("ERROR: Wave format xWMA only supports 16-bit data\n");
+ return false;
+ }
+
+ if (!wfx->nBlockAlign)
+ {
+ DebugTrace("ERROR: Wave format xWMA must have a non-zero nBlockAlign\n");
+ return false;
+ }
+
+ if (!wfx->nAvgBytesPerSec)
+ {
+ DebugTrace("ERROR: Wave format xWMA must have a non-zero nAvgBytesPerSec\n");
+ return false;
+ }
+
+ break;
+
+ #else
+ DebugTrace("ERROR: Wave format xWMA not supported by this version of DirectXTK for Audio\n");
+ return false;
+ #endif
+
+ case 0x166 /* WAVE_FORMAT_XMA2 */:
+ DebugTrace("ERROR: Wave format XMA2 is not supported as a WAVEFORMATEXTENSIBLE\n");
+ return false;
+
+ default:
+ DebugTrace("ERROR: Unknown WAVEFORMATEXTENSIBLE format tag (%u)\n", wfex->SubFormat.Data1);
+ return false;
+ }
+
+ if (wfex->dwChannelMask)
+ {
+ auto const channelBits = ChannelsSpecifiedInMask(wfex->dwChannelMask);
+ if (channelBits != wfx->nChannels)
+ {
+ DebugTrace("ERROR: WAVEFORMATEXTENSIBLE: nChannels=%u but ChannelMask has %u bits set\n",
+ wfx->nChannels, channelBits);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ default:
+ DebugTrace("ERROR: Unknown WAVEFORMATEX format tag (%u)\n", wfx->wFormatTag);
+ return false;
+ }
+}
+
+
+uint32_t DirectX::GetDefaultChannelMask(int channels) noexcept
+{
+ switch (channels)
+ {
+ case 1: return SPEAKER_MONO;
+ case 2: return SPEAKER_STEREO;
+ case 3: return SPEAKER_2POINT1;
+ case 4: return SPEAKER_QUAD;
+ case 5: return SPEAKER_4POINT1;
+ case 6: return SPEAKER_5POINT1;
+ case 7: return SPEAKER_5POINT1 | SPEAKER_BACK_CENTER;
+ case 8: return SPEAKER_7POINT1;
+ default: return 0;
+ }
+}
+
+
+_Use_decl_annotations_
+void DirectX::CreateIntegerPCM(
+ WAVEFORMATEX* wfx,
+ int sampleRate,
+ int channels,
+ int sampleBits) noexcept
+{
+ const int blockAlign = channels * sampleBits / 8;
+
+ wfx->wFormatTag = WAVE_FORMAT_PCM;
+ wfx->nChannels = static_cast(channels);
+ wfx->nSamplesPerSec = static_cast(sampleRate);
+ wfx->nAvgBytesPerSec = static_cast(blockAlign * sampleRate);
+ wfx->nBlockAlign = static_cast(blockAlign);
+ wfx->wBitsPerSample = static_cast(sampleBits);
+ wfx->cbSize = 0;
+
+ assert(IsValid(wfx));
+}
+
+
+_Use_decl_annotations_
+void DirectX::CreateFloatPCM(
+ WAVEFORMATEX* wfx,
+ int sampleRate,
+ int channels) noexcept
+{
+ const int blockAlign = channels * 4;
+
+ wfx->wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
+ wfx->nChannels = static_cast(channels);
+ wfx->nSamplesPerSec = static_cast(sampleRate);
+ wfx->nAvgBytesPerSec = static_cast(blockAlign * sampleRate);
+ wfx->nBlockAlign = static_cast(blockAlign);
+ wfx->wBitsPerSample = 32;
+ wfx->cbSize = 0;
+
+ assert(IsValid(wfx));
+}
+
+
+_Use_decl_annotations_
+void DirectX::CreateADPCM(
+ WAVEFORMATEX* wfx,
+ size_t wfxSize,
+ int sampleRate,
+ int channels,
+ int samplesPerBlock) noexcept(false)
+{
+ if (wfxSize < (sizeof(WAVEFORMATEX) + MSADPCM_FORMAT_EXTRA_BYTES))
+ {
+ DebugTrace("CreateADPCM needs at least %zu bytes for the result\n",
+ (sizeof(WAVEFORMATEX) + MSADPCM_FORMAT_EXTRA_BYTES));
+ throw std::invalid_argument("ADPCMWAVEFORMAT");
+ }
+
+ if (!samplesPerBlock)
+ {
+ DebugTrace("CreateADPCM needs a non-zero samples per block count\n");
+ throw std::invalid_argument("ADPCMWAVEFORMAT");
+ }
+
+ const int blockAlign = MSADPCM_HEADER_LENGTH * channels
+ + (samplesPerBlock - 2) * MSADPCM_BITS_PER_SAMPLE * channels / 8;
+
+ wfx->wFormatTag = WAVE_FORMAT_ADPCM;
+ wfx->nChannels = static_cast(channels);
+ wfx->nSamplesPerSec = static_cast(sampleRate);
+ wfx->nAvgBytesPerSec = static_cast(blockAlign * sampleRate / samplesPerBlock);
+ wfx->nBlockAlign = static_cast(blockAlign);
+ wfx->wBitsPerSample = MSADPCM_BITS_PER_SAMPLE;
+ wfx->cbSize = MSADPCM_FORMAT_EXTRA_BYTES;
+
+ auto adpcm = reinterpret_cast(wfx);
+ adpcm->wSamplesPerBlock = static_cast(samplesPerBlock);
+ adpcm->wNumCoef = MSADPCM_NUM_COEFFICIENTS;
+
+ static ADPCMCOEFSET aCoef[7] = { { 256, 0}, {512, -256}, {0,0}, {192,64}, {240,0}, {460, -208}, {392,-232} };
+ memcpy(&adpcm->aCoef, aCoef, sizeof(aCoef));
+
+ assert(IsValid(wfx));
+}
+
+
+#ifdef DIRECTX_ENABLE_XWMA
+_Use_decl_annotations_
+void DirectX::CreateXWMA(
+ WAVEFORMATEX* wfx,
+ int sampleRate,
+ int channels,
+ int blockAlign,
+ int avgBytes,
+ bool wma3) noexcept
+{
+ wfx->wFormatTag = static_cast((wma3) ? WAVE_FORMAT_WMAUDIO3 : WAVE_FORMAT_WMAUDIO2);
+ wfx->nChannels = static_cast(channels);
+ wfx->nSamplesPerSec = static_cast(sampleRate);
+ wfx->nAvgBytesPerSec = static_cast(avgBytes);
+ wfx->nBlockAlign = static_cast(blockAlign);
+ wfx->wBitsPerSample = 16;
+ wfx->cbSize = 0;
+
+ assert(IsValid(wfx));
+}
+#endif
+
+
+#ifdef DIRECTX_ENABLE_XMA2
+_Use_decl_annotations_
+void DirectX::CreateXMA2(
+ WAVEFORMATEX* wfx,
+ size_t wfxSize,
+ int sampleRate,
+ int channels,
+ int bytesPerBlock,
+ int blockCount,
+ int samplesEncoded) noexcept(false)
+{
+ if (wfxSize < sizeof(XMA2WAVEFORMATEX))
+ {
+ DebugTrace("XMA2 needs at least %zu bytes for the result\n", sizeof(XMA2WAVEFORMATEX));
+ throw std::invalid_argument("XMA2WAVEFORMATEX");
+ }
+
+ if ((bytesPerBlock < 1) || (bytesPerBlock > int(XMA_READBUFFER_MAX_BYTES)))
+ {
+ DebugTrace("XMA2 needs a valid bytes per block\n");
+ throw std::invalid_argument("XMA2WAVEFORMATEX");
+ }
+
+ int blockAlign = (channels * XMA_OUTPUT_SAMPLE_BITS) / 8;
+
+ wfx->wFormatTag = WAVE_FORMAT_XMA2;
+ wfx->nChannels = static_cast(channels);
+ wfx->nSamplesPerSec = static_cast(sampleRate);
+ wfx->nAvgBytesPerSec = static_cast(blockAlign * sampleRate);
+ wfx->nBlockAlign = static_cast(blockAlign);
+ wfx->wBitsPerSample = XMA_OUTPUT_SAMPLE_BITS;
+ wfx->cbSize = sizeof(XMA2WAVEFORMATEX) - sizeof(WAVEFORMATEX);
+
+ auto xmaFmt = reinterpret_cast(wfx);
+
+ xmaFmt->NumStreams = static_cast((channels + 1) / 2);
+
+ xmaFmt->ChannelMask = GetDefaultChannelMask(channels);
+
+ xmaFmt->SamplesEncoded = static_cast(samplesEncoded);
+ xmaFmt->BytesPerBlock = static_cast(bytesPerBlock);
+ xmaFmt->PlayBegin = xmaFmt->PlayLength =
+ xmaFmt->LoopBegin = xmaFmt->LoopLength = xmaFmt->LoopCount = 0;
+ xmaFmt->EncoderVersion = 4 /* XMAENCODER_VERSION_XMA2 */;
+ xmaFmt->BlockCount = static_cast(blockCount);
+
+ assert(IsValid(wfx));
+}
+#endif // XMA2
+
+
+_Use_decl_annotations_
+bool DirectX::ComputePan(float pan, unsigned int channels, float* matrix) noexcept
+{
+ memset(matrix, 0, sizeof(float) * 16);
+
+ if (channels == 1)
+ {
+ // Mono panning
+ float left = 1.f - pan;
+ left = std::min(1.f, left);
+ left = std::max(0.f, left);
+
+ float right = pan + 1.f;
+ right = std::min(1.f, right);
+ right = std::max(0.f, right);
+
+ matrix[0] = left;
+ matrix[1] = right;
+ }
+ else if (channels == 2)
+ {
+ // Stereo panning
+ if (-1.f <= pan && pan <= 0.f)
+ {
+ matrix[0] = .5f * pan + 1.f; // .5 when pan is -1, 1 when pan is 0
+ matrix[1] = .5f * -pan; // .5 when pan is -1, 0 when pan is 0
+ matrix[2] = 0.f; // 0 when pan is -1, 0 when pan is 0
+ matrix[3] = pan + 1.f; // 0 when pan is -1, 1 when pan is 0
+ }
+ else
+ {
+ matrix[0] = -pan + 1.f; // 1 when pan is 0, 0 when pan is 1
+ matrix[1] = 0.f; // 0 when pan is 0, 0 when pan is 1
+ matrix[2] = .5f * pan; // 0 when pan is 0, .5f when pan is 1
+ matrix[3] = .5f * -pan + 1.f; // 1 when pan is 0. .5f when pan is 1
+ }
+ }
+ else
+ {
+ if (pan != 0.f)
+ {
+ DebugTrace("WARNING: Only supports panning on mono or stereo source data, ignored\n");
+ }
+ return false;
+ }
+
+ return true;
+}
+
+
+//======================================================================================
+// SoundEffectInstanceBase
+//======================================================================================
+
+void SoundEffectInstanceBase::SetPan(float pan)
+{
+ assert(pan >= -1.f && pan <= 1.f);
+
+ mPan = pan;
+
+ if (!voice)
+ return;
+
+ float matrix[16];
+ if (ComputePan(pan, mDSPSettings.SrcChannelCount, matrix))
+ {
+ HRESULT hr = voice->SetOutputMatrix(nullptr, mDSPSettings.SrcChannelCount, mDSPSettings.DstChannelCount, matrix);
+ ThrowIfFailed(hr);
+ }
+}
+
+
+void SoundEffectInstanceBase::Apply3D(const X3DAUDIO_LISTENER& listener, const X3DAUDIO_EMITTER& emitter, bool rhcoords)
+{
+ if (!voice)
+ return;
+
+ if (!(mFlags & SoundEffectInstance_Use3D))
+ {
+ DebugTrace("ERROR: Apply3D called for an instance created without SoundEffectInstance_Use3D set\n");
+ throw std::runtime_error("Apply3D");
+ }
+
+ DWORD dwCalcFlags = X3DAUDIO_CALCULATE_MATRIX | X3DAUDIO_CALCULATE_DOPPLER | X3DAUDIO_CALCULATE_LPF_DIRECT;
+
+ if (mFlags & SoundEffectInstance_UseRedirectLFE)
+ {
+ // On devices with an LFE channel, allow the mono source data to be routed to the LFE destination channel.
+ dwCalcFlags |= X3DAUDIO_CALCULATE_REDIRECT_TO_LFE;
+ }
+
+ auto reverb = mReverbVoice;
+ if (reverb)
+ {
+ dwCalcFlags |= X3DAUDIO_CALCULATE_LPF_REVERB | X3DAUDIO_CALCULATE_REVERB;
+ }
+
+ float matrix[XAUDIO2_MAX_AUDIO_CHANNELS * 8] = {};
+ assert(mDSPSettings.SrcChannelCount <= XAUDIO2_MAX_AUDIO_CHANNELS);
+ assert(mDSPSettings.DstChannelCount <= 8);
+ mDSPSettings.pMatrixCoefficients = matrix;
+
+ assert(engine != nullptr);
+ if (rhcoords)
+ {
+ X3DAUDIO_EMITTER lhEmitter;
+ memcpy(&lhEmitter, &emitter, sizeof(X3DAUDIO_EMITTER));
+ lhEmitter.OrientFront.z = -emitter.OrientFront.z;
+ lhEmitter.OrientTop.z = -emitter.OrientTop.z;
+ lhEmitter.Position.z = -emitter.Position.z;
+ lhEmitter.Velocity.z = -emitter.Velocity.z;
+
+ X3DAUDIO_LISTENER lhListener;
+ memcpy(&lhListener, &listener, sizeof(X3DAUDIO_LISTENER));
+ lhListener.OrientFront.z = -listener.OrientFront.z;
+ lhListener.OrientTop.z = -listener.OrientTop.z;
+ lhListener.Position.z = -listener.Position.z;
+ lhListener.Velocity.z = -listener.Velocity.z;
+
+ X3DAudioCalculate(engine->Get3DHandle(), &lhListener, &lhEmitter, dwCalcFlags, &mDSPSettings);
+ }
+ else
+ {
+ X3DAudioCalculate(engine->Get3DHandle(), &listener, &emitter, dwCalcFlags, &mDSPSettings);
+ }
+
+ mDSPSettings.pMatrixCoefficients = nullptr;
+
+ std::ignore = voice->SetFrequencyRatio(mFreqRatio * mDSPSettings.DopplerFactor);
+
+ auto direct = mDirectVoice;
+ assert(direct != nullptr);
+ std::ignore = voice->SetOutputMatrix(direct, mDSPSettings.SrcChannelCount, mDSPSettings.DstChannelCount, matrix);
+
+ if (reverb)
+ {
+ for (size_t j = 0; (j < mDSPSettings.SrcChannelCount) && (j < XAUDIO2_MAX_AUDIO_CHANNELS); ++j)
+ {
+ matrix[j] = mDSPSettings.ReverbLevel;
+ }
+ std::ignore = voice->SetOutputMatrix(reverb, mDSPSettings.SrcChannelCount, 1, matrix);
+ }
+
+ if (mFlags & SoundEffectInstance_ReverbUseFilters)
+ {
+ XAUDIO2_FILTER_PARAMETERS filterDirect = { LowPassFilter, 2.0f * sinf(X3DAUDIO_PI / 6.0f * mDSPSettings.LPFDirectCoefficient), 1.0f };
+ // see XAudio2CutoffFrequencyToRadians() in XAudio2.h for more information on the formula used here
+ std::ignore = voice->SetOutputFilterParameters(direct, &filterDirect);
+
+ if (reverb)
+ {
+ XAUDIO2_FILTER_PARAMETERS filterReverb = { LowPassFilter, 2.0f * sinf(X3DAUDIO_PI / 6.0f * mDSPSettings.LPFReverbCoefficient), 1.0f };
+ // see XAudio2CutoffFrequencyToRadians() in XAudio2.h for more information on the formula used here
+ std::ignore = voice->SetOutputFilterParameters(reverb, &filterReverb);
+ }
+ }
+}
+
+
+//======================================================================================
+// AudioListener/Emitter helpers
+//======================================================================================
+
+namespace
+{
+ inline bool IsValid(const X3DAUDIO_CONE& cone) noexcept
+ {
+ // These match the validation ranges in X3DAudio.
+ if (cone.InnerAngle < 0.f || cone.InnerAngle > X3DAUDIO_2PI)
+ return false;
+
+ if (cone.OuterAngle < 0.f || cone.OuterAngle > X3DAUDIO_2PI)
+ return false;
+
+ if (cone.InnerAngle > cone.OuterAngle)
+ return false;
+
+ if (cone.InnerVolume < 0.f || cone.InnerVolume > 2.f)
+ return false;
+
+ if (cone.OuterVolume < 0.f || cone.OuterVolume > 2.f)
+ return false;
+
+ if (cone.InnerLPF < 0.f || cone.InnerLPF > 1.f)
+ return false;
+
+ if (cone.OuterLPF < 0.f || cone.OuterLPF > 1.f)
+ return false;
+
+ if (cone.InnerReverb < 0.f || cone.InnerReverb > 2.f)
+ return false;
+
+ if (cone.OuterReverb < 0.f || cone.OuterReverb > 2.f)
+ return false;
+
+ return true;
+ }
+}
+
+void AudioListener::SetCone(const X3DAUDIO_CONE& listenerCone)
+{
+ if (!::IsValid(listenerCone))
+ throw std::invalid_argument("X3DAUDIO_CONE values out of range");
+
+ ListenerCone = listenerCone;
+ pCone = &ListenerCone;
+}
+
+void AudioEmitter::SetCone(const X3DAUDIO_CONE& emitterCone)
+{
+ if (!::IsValid(emitterCone))
+ throw std::invalid_argument("X3DAUDIO_CONE values out of range");
+
+ EmitterCone = emitterCone;
+ pCone = &EmitterCone;
+}
+
+namespace
+{
+ // **Note these constants came from xact3d3.h in the legacy DirectX SDK**
+ //
+ // Supported speaker positions, represented as azimuth angles.
+ //
+ // Here's a picture of the azimuth angles for the 8 cardinal points,
+ // seen from above. The emitter's base position is at the origin 0.
+ //
+ // FRONT
+ // | 0 <-- azimuth
+ // |
+ // 7pi/4 \ | / pi/4
+ // \ | /
+ // LEFT \|/ RIGHT
+ // 3pi/2-------0-------pi/2
+ // /|\
+ // / | \
+ // 5pi/4 / | \ 3pi/4
+ // |
+ // | pi
+ // BACK
+ //
+
+ constexpr float LEFT_AZIMUTH = 3 * X3DAUDIO_PI / 2;
+ constexpr float RIGHT_AZIMUTH = X3DAUDIO_PI / 2;
+ constexpr float FRONT_LEFT_AZIMUTH = 7 * X3DAUDIO_PI / 4;
+ constexpr float FRONT_RIGHT_AZIMUTH = X3DAUDIO_PI / 4;
+ constexpr float FRONT_CENTER_AZIMUTH = 0.0f;
+ constexpr float LOW_FREQUENCY_AZIMUTH = X3DAUDIO_2PI;
+ constexpr float BACK_LEFT_AZIMUTH = 5 * X3DAUDIO_PI / 4;
+ constexpr float BACK_RIGHT_AZIMUTH = 3 * X3DAUDIO_PI / 4;
+ constexpr float BACK_CENTER_AZIMUTH = X3DAUDIO_PI;
+
+ constexpr float c_channelAzimuths[9][8] =
+ {
+ /* 0 */ { 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f },
+ /* 1 */ { 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f },
+ /* 2 */ { FRONT_LEFT_AZIMUTH, FRONT_RIGHT_AZIMUTH, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f },
+ /* 2.1 */ { FRONT_LEFT_AZIMUTH, FRONT_RIGHT_AZIMUTH, LOW_FREQUENCY_AZIMUTH, 0.f, 0.f, 0.f, 0.f, 0.f },
+ /* 4.0 */ { FRONT_LEFT_AZIMUTH, FRONT_RIGHT_AZIMUTH, BACK_LEFT_AZIMUTH, BACK_RIGHT_AZIMUTH, 0.f, 0.f, 0.f, 0.f },
+ /* 4.1 */ { FRONT_LEFT_AZIMUTH, FRONT_RIGHT_AZIMUTH, LOW_FREQUENCY_AZIMUTH, BACK_LEFT_AZIMUTH, BACK_RIGHT_AZIMUTH, 0.f, 0.f, 0.f },
+ /* 5.1 */ { FRONT_LEFT_AZIMUTH, FRONT_RIGHT_AZIMUTH, FRONT_CENTER_AZIMUTH, LOW_FREQUENCY_AZIMUTH, BACK_LEFT_AZIMUTH, BACK_RIGHT_AZIMUTH, 0.f, 0.f },
+ /* 6.1 */ { FRONT_LEFT_AZIMUTH, FRONT_RIGHT_AZIMUTH, FRONT_CENTER_AZIMUTH, LOW_FREQUENCY_AZIMUTH, BACK_LEFT_AZIMUTH, BACK_RIGHT_AZIMUTH, BACK_CENTER_AZIMUTH, 0.f },
+ /* 7.1 */ { FRONT_LEFT_AZIMUTH, FRONT_RIGHT_AZIMUTH, FRONT_CENTER_AZIMUTH, LOW_FREQUENCY_AZIMUTH, BACK_LEFT_AZIMUTH, BACK_RIGHT_AZIMUTH, LEFT_AZIMUTH, RIGHT_AZIMUTH }
+ };
+}
+
+void AudioEmitter::EnableDefaultMultiChannel(unsigned int channels, float radius)
+{
+ if (channels > XAUDIO2_MAX_AUDIO_CHANNELS)
+ throw std::invalid_argument("Invalid channel count");
+
+ ChannelCount = channels;
+ ChannelRadius = radius;
+ pChannelAzimuths = EmitterAzimuths;
+
+ if (channels <= 8)
+ {
+ memcpy(EmitterAzimuths, &c_channelAzimuths[channels][0], sizeof(float) * 8);
+ }
+ else
+ {
+ memset(EmitterAzimuths, 0, sizeof(float) * size_t(channels));
+ }
+}
+
+namespace
+{
+ // **Note these match the defaults from xact3d3.h in the legacy DirectX SDK**
+ constexpr X3DAUDIO_DISTANCE_CURVE_POINT c_defaultCurvePoints[2] = { { 0.0f, 1.0f }, { 1.0f, 1.0f } };
+ constexpr X3DAUDIO_DISTANCE_CURVE c_defaultCurve = { const_cast(c_defaultCurvePoints), 2 };
+
+ // **Note these match X3DAudioDefault_LinearCurvePoints from x3daudio.h**
+ constexpr X3DAUDIO_DISTANCE_CURVE_POINT c_linearCurvePoints[2] = { { 0.0f, 1.0f }, { 1.0f, 0.0f } };
+ constexpr X3DAUDIO_DISTANCE_CURVE c_linearCurve = { const_cast(c_linearCurvePoints), 2 };
+}
+
+void AudioEmitter::EnableDefaultCurves() noexcept
+{
+ pVolumeCurve = const_cast(&c_defaultCurve);
+ pLFECurve = const_cast(&c_defaultCurve);
+ pLPFDirectCurve = pLPFReverbCurve = pReverbCurve = nullptr;
+}
+
+void AudioEmitter::EnableLinearCurves() noexcept
+{
+ pVolumeCurve = const_cast(&c_linearCurve);
+ pLFECurve = const_cast(&c_linearCurve);
+ pLPFDirectCurve = pLPFReverbCurve = pReverbCurve = nullptr;
+}
diff --git a/Common/DirectXTK12/Audio/SoundCommon.h b/Common/DirectXTK12/Audio/SoundCommon.h
new file mode 100644
index 0000000..1fefe19
--- /dev/null
+++ b/Common/DirectXTK12/Audio/SoundCommon.h
@@ -0,0 +1,393 @@
+//--------------------------------------------------------------------------------------
+// File: SoundCommon.h
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+// http://go.microsoft.com/fwlink/?LinkId=248929
+// http://go.microsoft.com/fwlink/?LinkID=615561
+//--------------------------------------------------------------------------------------
+
+#pragma once
+
+#include "Audio.h"
+#include "PlatformHelpers.h"
+
+#ifdef USING_XAUDIO2_9
+#define DIRECTX_ENABLE_XWMA
+#endif
+
+#if (defined(_XBOX_ONE) && defined(_TITLE)) || defined(_GAMING_XBOX)
+#define DIRECTX_ENABLE_XMA2
+#endif
+
+#if defined(DIRECTX_ENABLE_XWMA) || defined(DIRECTX_ENABLE_XMA2)
+#define DIRECTX_ENABLE_SEEK_TABLES
+#endif
+
+namespace DirectX
+{
+ // Helper for getting a format tag from a WAVEFORMATEX
+ inline uint32_t GetFormatTag(const WAVEFORMATEX* wfx) noexcept
+ {
+ if (wfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
+ {
+ if (wfx->cbSize < (sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)))
+ return 0;
+
+ static const GUID s_wfexBase = { 0x00000000, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 } };
+
+ auto wfex = reinterpret_cast(wfx);
+
+ if (memcmp(reinterpret_cast(&wfex->SubFormat) + sizeof(DWORD),
+ reinterpret_cast(&s_wfexBase) + sizeof(DWORD), sizeof(GUID) - sizeof(DWORD)) != 0)
+ {
+ return 0;
+ }
+
+ return wfex->SubFormat.Data1;
+ }
+ else
+ {
+ return wfx->wFormatTag;
+ }
+ }
+
+
+ // Helper for validating wave format structure
+ bool IsValid(_In_ const WAVEFORMATEX* wfx) noexcept;
+
+
+ // Helper for getting a default channel mask from channels
+ uint32_t GetDefaultChannelMask(int channels) noexcept;
+
+
+ // Helpers for creating various wave format structures
+ void CreateIntegerPCM(_Out_ WAVEFORMATEX* wfx,
+ int sampleRate, int channels, int sampleBits) noexcept;
+ void CreateFloatPCM(_Out_ WAVEFORMATEX* wfx,
+ int sampleRate, int channels) noexcept;
+ void CreateADPCM(_Out_writes_bytes_(wfxSize) WAVEFORMATEX* wfx, size_t wfxSize,
+ int sampleRate, int channels, int samplesPerBlock) noexcept(false);
+#ifdef DIRECTX_ENABLE_XWMA
+ void CreateXWMA(_Out_ WAVEFORMATEX* wfx,
+ int sampleRate, int channels, int blockAlign, int avgBytes, bool wma3) noexcept;
+#endif
+#ifdef DIRECTX_ENABLE_XMA2
+ void CreateXMA2(_Out_writes_bytes_(wfxSize) WAVEFORMATEX* wfx, size_t wfxSize,
+ int sampleRate, int channels, int bytesPerBlock, int blockCount, int samplesEncoded) noexcept(false);
+#endif
+
+ // Helper for computing pan volume matrix
+ bool ComputePan(float pan, unsigned int channels, _Out_writes_(16) float* matrix) noexcept;
+
+ // Helper class for implementing SoundEffectInstance
+ class SoundEffectInstanceBase
+ {
+ public:
+ SoundEffectInstanceBase() noexcept :
+ voice(nullptr),
+ state(STOPPED),
+ engine(nullptr),
+ mVolume(1.f),
+ mPitch(0.f),
+ mFreqRatio(1.f),
+ mPan(0.f),
+ mFlags(SoundEffectInstance_Default),
+ mDirectVoice(nullptr),
+ mReverbVoice(nullptr),
+ mDSPSettings{}
+ {
+ }
+
+ SoundEffectInstanceBase(SoundEffectInstanceBase&&) = default;
+ SoundEffectInstanceBase& operator= (SoundEffectInstanceBase&&) = default;
+
+ SoundEffectInstanceBase(SoundEffectInstanceBase const&) = delete;
+ SoundEffectInstanceBase& operator= (SoundEffectInstanceBase const&) = delete;
+
+ ~SoundEffectInstanceBase()
+ {
+ assert(voice == nullptr);
+ }
+
+ void Initialize(_In_ AudioEngine* eng, _In_ const WAVEFORMATEX* wfx, SOUND_EFFECT_INSTANCE_FLAGS flags) noexcept
+ {
+ assert(eng != nullptr);
+ engine = eng;
+ mDirectVoice = eng->GetMasterVoice();
+ mReverbVoice = eng->GetReverbVoice();
+
+ if (eng->GetChannelMask() & SPEAKER_LOW_FREQUENCY)
+ mFlags = flags | SoundEffectInstance_UseRedirectLFE;
+ else
+ mFlags = flags & ~SoundEffectInstance_UseRedirectLFE;
+
+ memset(&mDSPSettings, 0, sizeof(X3DAUDIO_DSP_SETTINGS));
+ assert(wfx != nullptr);
+ mDSPSettings.SrcChannelCount = wfx->nChannels;
+ mDSPSettings.DstChannelCount = eng->GetOutputChannels();
+ }
+
+ void AllocateVoice(_In_ const WAVEFORMATEX* wfx)
+ {
+ if (voice)
+ return;
+
+ assert(engine != nullptr);
+ engine->AllocateVoice(wfx, mFlags, false, &voice);
+ }
+
+ void DestroyVoice() noexcept
+ {
+ if (voice)
+ {
+ assert(engine != nullptr);
+ engine->DestroyVoice(voice);
+ voice = nullptr;
+ }
+ }
+
+ bool Play() // Returns true if STOPPED -> PLAYING
+ {
+ if (voice)
+ {
+ if (state == PAUSED)
+ {
+ HRESULT hr = voice->Start(0);
+ ThrowIfFailed(hr);
+ state = PLAYING;
+ }
+ else if (state != PLAYING)
+ {
+ if (mVolume != 1.f)
+ {
+ HRESULT hr = voice->SetVolume(mVolume);
+ ThrowIfFailed(hr);
+ }
+
+ if (mPitch != 0.f)
+ {
+ mFreqRatio = XAudio2SemitonesToFrequencyRatio(mPitch * 12.f);
+
+ HRESULT hr = voice->SetFrequencyRatio(mFreqRatio);
+ ThrowIfFailed(hr);
+ }
+
+ if (mPan != 0.f)
+ {
+ SetPan(mPan);
+ }
+
+ HRESULT hr = voice->Start(0);
+ ThrowIfFailed(hr);
+ state = PLAYING;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void Stop(bool immediate, bool& looped) noexcept
+ {
+ if (!voice)
+ {
+ state = STOPPED;
+ return;
+ }
+
+ if (immediate)
+ {
+ state = STOPPED;
+ std::ignore = voice->Stop(0);
+ std::ignore = voice->FlushSourceBuffers();
+ }
+ else if (looped)
+ {
+ looped = false;
+ std::ignore = voice->ExitLoop();
+ }
+ else
+ {
+ std::ignore = voice->Stop(XAUDIO2_PLAY_TAILS);
+ }
+ }
+
+ void Pause() noexcept
+ {
+ if (voice && state == PLAYING)
+ {
+ state = PAUSED;
+
+ std::ignore = voice->Stop(0);
+ }
+ }
+
+ void Resume()
+ {
+ if (voice && state == PAUSED)
+ {
+ HRESULT hr = voice->Start(0);
+ ThrowIfFailed(hr);
+ state = PLAYING;
+ }
+ }
+
+ void SetVolume(float volume)
+ {
+ assert(volume >= -XAUDIO2_MAX_VOLUME_LEVEL && volume <= XAUDIO2_MAX_VOLUME_LEVEL);
+
+ mVolume = volume;
+
+ if (voice)
+ {
+ HRESULT hr = voice->SetVolume(volume);
+ ThrowIfFailed(hr);
+ }
+ }
+
+ void SetPitch(float pitch)
+ {
+ assert(pitch >= -1.f && pitch <= 1.f);
+
+ if ((mFlags & SoundEffectInstance_NoSetPitch) && pitch != 0.f)
+ {
+ DebugTrace("ERROR: Sound effect instance was created with the NoSetPitch flag\n");
+ throw std::runtime_error("SetPitch");
+ }
+
+ mPitch = pitch;
+
+ if (voice)
+ {
+ mFreqRatio = XAudio2SemitonesToFrequencyRatio(mPitch * 12.f);
+
+ HRESULT hr = voice->SetFrequencyRatio(mFreqRatio);
+ ThrowIfFailed(hr);
+ }
+ }
+
+ void SetPan(float pan);
+
+ void Apply3D(const X3DAUDIO_LISTENER& listener, const X3DAUDIO_EMITTER& emitter, bool rhcoords);
+
+ SoundState GetState(bool autostop) noexcept
+ {
+ if (autostop && voice && (state == PLAYING))
+ {
+ XAUDIO2_VOICE_STATE xstate;
+ voice->GetState(&xstate, XAUDIO2_VOICE_NOSAMPLESPLAYED);
+
+ if (!xstate.BuffersQueued)
+ {
+ // Automatic stop if the buffer has finished playing
+ std::ignore = voice->Stop();
+ state = STOPPED;
+ }
+ }
+
+ return state;
+ }
+
+ int GetPendingBufferCount() const noexcept
+ {
+ if (!voice)
+ return 0;
+
+ XAUDIO2_VOICE_STATE xstate;
+ voice->GetState(&xstate, XAUDIO2_VOICE_NOSAMPLESPLAYED);
+ return static_cast(xstate.BuffersQueued);
+ }
+
+ unsigned int GetChannelCount() const noexcept
+ {
+ return mDSPSettings.SrcChannelCount;
+ }
+
+ void OnCriticalError() noexcept
+ {
+ if (voice)
+ {
+ voice->DestroyVoice();
+ voice = nullptr;
+ }
+ state = STOPPED;
+ mDirectVoice = nullptr;
+ mReverbVoice = nullptr;
+ }
+
+ void OnReset() noexcept
+ {
+ assert(engine != nullptr);
+ mDirectVoice = engine->GetMasterVoice();
+ mReverbVoice = engine->GetReverbVoice();
+
+ if (engine->GetChannelMask() & SPEAKER_LOW_FREQUENCY)
+ mFlags = mFlags | SoundEffectInstance_UseRedirectLFE;
+ else
+ mFlags = mFlags & ~SoundEffectInstance_UseRedirectLFE;
+
+ mDSPSettings.DstChannelCount = engine->GetOutputChannels();
+ }
+
+ void OnDestroy() noexcept
+ {
+ if (voice)
+ {
+ std::ignore = voice->Stop(0);
+ std::ignore = voice->FlushSourceBuffers();
+ voice->DestroyVoice();
+ voice = nullptr;
+ }
+ state = STOPPED;
+ engine = nullptr;
+ mDirectVoice = nullptr;
+ mReverbVoice = nullptr;
+ }
+
+ void OnTrim()
+ {
+ if (voice && (state == STOPPED))
+ {
+ engine->DestroyVoice(voice);
+ voice = nullptr;
+ }
+ }
+
+ void GatherStatistics(AudioStatistics& stats) const noexcept
+ {
+ ++stats.allocatedInstances;
+ if (voice)
+ {
+ ++stats.allocatedVoices;
+
+ if (mFlags & SoundEffectInstance_Use3D)
+ ++stats.allocatedVoices3d;
+
+ if (state == PLAYING)
+ ++stats.playingInstances;
+ }
+ }
+
+ IXAudio2SourceVoice* voice;
+ SoundState state;
+ AudioEngine* engine;
+
+ private:
+ float mVolume;
+ float mPitch;
+ float mFreqRatio;
+ float mPan;
+ SOUND_EFFECT_INSTANCE_FLAGS mFlags;
+ IXAudio2Voice* mDirectVoice;
+ IXAudio2Voice* mReverbVoice;
+ X3DAUDIO_DSP_SETTINGS mDSPSettings;
+ };
+
+ struct WaveBankSeekData
+ {
+ uint32_t seekCount;
+ const uint32_t* seekTable;
+ uint32_t tag;
+ };
+}
diff --git a/Common/DirectXTK12/Audio/SoundEffect.cpp b/Common/DirectXTK12/Audio/SoundEffect.cpp
new file mode 100644
index 0000000..5985212
--- /dev/null
+++ b/Common/DirectXTK12/Audio/SoundEffect.cpp
@@ -0,0 +1,629 @@
+//--------------------------------------------------------------------------------------
+// File: SoundEffect.cpp
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+// http://go.microsoft.com/fwlink/?LinkId=248929
+// http://go.microsoft.com/fwlink/?LinkID=615561
+//--------------------------------------------------------------------------------------
+
+#include "pch.h"
+#include "WAVFileReader.h"
+#include "SoundCommon.h"
+
+#include
+
+#if (defined(_XBOX_ONE) && defined(_TITLE)) || defined(_GAMING_XBOX)
+#ifdef __clang__
+#pragma clang diagnostic ignored "-Wnonportable-system-include-path"
+#endif
+
+#include
+#include
+#endif
+
+using namespace DirectX;
+
+
+//======================================================================================
+// SoundEffect
+//======================================================================================
+
+// Internal object implementation class.
+class SoundEffect::Impl : public IVoiceNotify
+{
+public:
+ explicit Impl(_In_ AudioEngine* engine) :
+ mWaveFormat(nullptr),
+ mStartAudio(nullptr),
+ mAudioBytes(0),
+ mLoopStart(0),
+ mLoopLength(0),
+ mEngine(engine),
+ mOneShots(0)
+ #ifdef DIRECTX_ENABLE_SEEK_TABLES
+ , mSeekCount(0)
+ , mSeekTable(nullptr)
+ #endif
+ #ifdef DIRECTX_ENABLE_XMA2
+ , mXMAMemory(nullptr)
+ #endif
+ {
+ assert(mEngine != nullptr);
+ mEngine->RegisterNotify(this, false);
+ }
+
+ Impl(Impl&&) = default;
+ Impl& operator= (Impl&&) = default;
+
+ Impl(Impl const&) = delete;
+ Impl& operator= (Impl const&) = delete;
+
+ ~Impl() override
+ {
+ if (!mInstances.empty())
+ {
+ DebugTrace("WARNING: Destroying SoundEffect with %zu outstanding SoundEffectInstances\n", mInstances.size());
+
+ for (auto it : mInstances)
+ {
+ assert(it != nullptr);
+ it->OnDestroyParent();
+ }
+
+ mInstances.clear();
+ }
+
+ if (mOneShots > 0)
+ {
+ DebugTrace("WARNING: Destroying SoundEffect with %u outstanding one shot effects\n", mOneShots);
+ }
+
+ if (mEngine)
+ {
+ mEngine->UnregisterNotify(this, true, false);
+ mEngine = nullptr;
+ }
+
+ #ifdef DIRECTX_ENABLE_XMA2
+ if (mXMAMemory)
+ {
+ ApuFree(mXMAMemory);
+ mXMAMemory = nullptr;
+ }
+ #endif
+ }
+
+ HRESULT Initialize(
+ _In_ const AudioEngine* engine,
+ _Inout_ std::unique_ptr& wavData,
+ _In_ const WAVEFORMATEX* wfx,
+ _In_reads_bytes_(audioBytes) const uint8_t* startAudio, size_t audioBytes,
+ #ifdef DIRECTX_ENABLE_SEEK_TABLES
+ _In_reads_opt_(seekCount) const uint32_t* seekTable, size_t seekCount,
+ #endif
+ uint32_t loopStart, uint32_t loopLength) noexcept;
+
+ void Play(float volume, float pitch, float pan);
+
+ // IVoiceNotify
+ void __cdecl OnBufferEnd() override
+ {
+ InterlockedDecrement(&mOneShots);
+ }
+
+ void __cdecl OnCriticalError() override
+ {
+ mOneShots = 0;
+ }
+
+ void __cdecl OnReset() override
+ {
+ // No action required
+ }
+
+ void __cdecl OnUpdate() override
+ {
+ // We do not register for update notification
+ assert(false);
+ }
+
+ void __cdecl OnDestroyEngine() noexcept override
+ {
+ mEngine = nullptr;
+ mOneShots = 0;
+ }
+
+ void __cdecl OnTrim() override
+ {
+ // No action required
+ }
+
+ void __cdecl GatherStatistics(AudioStatistics& stats) const noexcept override
+ {
+ stats.playingOneShots += mOneShots;
+ stats.audioBytes += mAudioBytes;
+
+ #ifdef DIRECTX_ENABLE_XMA2
+ if (mXMAMemory)
+ stats.xmaAudioBytes += mAudioBytes;
+ #endif
+ }
+
+ void __cdecl OnDestroyParent() noexcept override
+ {
+ }
+
+ const WAVEFORMATEX* mWaveFormat;
+ const uint8_t* mStartAudio;
+ uint32_t mAudioBytes;
+ uint32_t mLoopStart;
+ uint32_t mLoopLength;
+ AudioEngine* mEngine;
+ std::list mInstances;
+ uint32_t mOneShots;
+
+#ifdef DIRECTX_ENABLE_SEEK_TABLES
+ uint32_t mSeekCount;
+ const uint32_t* mSeekTable;
+#endif
+
+private:
+ std::unique_ptr mWavData;
+
+#ifdef DIRECTX_ENABLE_XMA2
+ void* mXMAMemory;
+#endif
+};
+
+
+_Use_decl_annotations_
+HRESULT SoundEffect::Impl::Initialize(
+ const AudioEngine* engine,
+ std::unique_ptr& wavData,
+ const WAVEFORMATEX* wfx,
+ const uint8_t* startAudio, size_t audioBytes,
+#ifdef DIRECTX_ENABLE_SEEK_TABLES
+ const uint32_t* seekTable, size_t seekCount,
+#endif
+ uint32_t loopStart, uint32_t loopLength) noexcept
+{
+ if (!engine || !IsValid(wfx) || !startAudio || !audioBytes || !wavData)
+ return E_INVALIDARG;
+
+ if (audioBytes > UINT32_MAX)
+ return E_INVALIDARG;
+
+ switch (GetFormatTag(wfx))
+ {
+ case WAVE_FORMAT_PCM:
+ case WAVE_FORMAT_IEEE_FLOAT:
+ case WAVE_FORMAT_ADPCM:
+ // Take ownership of the buffer
+ mWavData.reset(wavData.release());
+
+ // WARNING: We assume the wfx and startAudio parameters are pointers into the wavData memory buffer
+ mWaveFormat = wfx;
+ mStartAudio = startAudio;
+ break;
+
+ #ifdef DIRECTX_ENABLE_XWMA
+
+ case WAVE_FORMAT_WMAUDIO2:
+ case WAVE_FORMAT_WMAUDIO3:
+ if (!seekCount || !seekTable)
+ {
+ DebugTrace("ERROR: SoundEffect format xWMA requires seek table\n");
+ return E_FAIL;
+ }
+
+ if (seekCount > UINT32_MAX)
+ return E_INVALIDARG;
+
+ // Take ownership of the buffer
+ mWavData.reset(wavData.release());
+
+ // WARNING: We assume the wfx, startAudio, and mSeekTable parameters are pointers into the wavData memory buffer
+ mWaveFormat = wfx;
+ mStartAudio = startAudio;
+ mSeekCount = static_cast(seekCount);
+ mSeekTable = seekTable;
+ break;
+
+ #endif // xWMA
+
+ #ifdef DIRECTX_ENABLE_XMA2
+
+ case WAVE_FORMAT_XMA2:
+ if (!seekCount || !seekTable)
+ {
+ DebugTrace("ERROR: SoundEffect format XMA2 requires seek table\n");
+ return E_FAIL;
+ }
+
+ if (seekCount > UINT32_MAX)
+ return E_INVALIDARG;
+
+ {
+ HRESULT hr = ApuAlloc(&mXMAMemory, nullptr,
+ static_cast(audioBytes), SHAPE_XMA_INPUT_BUFFER_ALIGNMENT);
+ if (FAILED(hr))
+ {
+ DebugTrace("ERROR: ApuAlloc failed. Did you allocate a large enough heap with ApuCreateHeap for all your XMA wave data?\n");
+ return hr;
+ }
+ }
+
+ memcpy(mXMAMemory, startAudio, audioBytes);
+ mStartAudio = reinterpret_cast(mXMAMemory);
+
+ mWavData.reset(new (std::nothrow) uint8_t[sizeof(XMA2WAVEFORMATEX) + (seekCount * sizeof(uint32_t))]);
+ if (!mWavData)
+ return E_OUTOFMEMORY;
+
+ memcpy(mWavData.get(), wfx, sizeof(XMA2WAVEFORMATEX));
+ mWaveFormat = reinterpret_cast(mWavData.get());
+
+ // XMA seek table is Big-Endian
+ {
+ auto dest = reinterpret_cast(mWavData.get() + sizeof(XMA2WAVEFORMATEX));
+ for (size_t k = 0; k < seekCount; ++k)
+ {
+ dest[k] = _byteswap_ulong(seekTable[k]);
+ }
+ }
+
+ mSeekCount = static_cast(seekCount);
+ mSeekTable = reinterpret_cast(mWavData.get() + sizeof(XMA2WAVEFORMATEX));
+
+ wavData.reset();
+ break;
+
+ #endif // XMA2
+
+ default:
+ {
+ DebugTrace("ERROR: SoundEffect encountered an unsupported format tag (%u)\n", wfx->wFormatTag);
+ return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
+ }
+ }
+
+ mAudioBytes = static_cast(audioBytes);
+ mLoopStart = loopStart;
+ mLoopLength = loopLength;
+
+ return S_OK;
+}
+
+
+void SoundEffect::Impl::Play(float volume, float pitch, float pan)
+{
+ assert(volume >= -XAUDIO2_MAX_VOLUME_LEVEL && volume <= XAUDIO2_MAX_VOLUME_LEVEL);
+ assert(pitch >= -1.f && pitch <= 1.f);
+ assert(pan >= -1.f && pan <= 1.f);
+
+ IXAudio2SourceVoice* voice = nullptr;
+ mEngine->AllocateVoice(mWaveFormat, SoundEffectInstance_Default, true, &voice);
+
+ if (!voice)
+ return;
+
+ if (volume != 1.f)
+ {
+ HRESULT hr = voice->SetVolume(volume);
+ ThrowIfFailed(hr);
+ }
+
+ if (pitch != 0.f)
+ {
+ const float fr = XAudio2SemitonesToFrequencyRatio(pitch * 12.f);
+
+ HRESULT hr = voice->SetFrequencyRatio(fr);
+ ThrowIfFailed(hr);
+ }
+
+ if (pan != 0.f)
+ {
+ float matrix[16];
+ if (ComputePan(pan, mWaveFormat->nChannels, matrix))
+ {
+ HRESULT hr = voice->SetOutputMatrix(nullptr, mWaveFormat->nChannels, mEngine->GetOutputChannels(), matrix);
+ ThrowIfFailed(hr);
+ }
+ }
+
+ HRESULT hr = voice->Start(0);
+ ThrowIfFailed(hr);
+
+ XAUDIO2_BUFFER buffer = {};
+ buffer.AudioBytes = mAudioBytes;
+ buffer.pAudioData = mStartAudio;
+ buffer.Flags = XAUDIO2_END_OF_STREAM;
+ buffer.pContext = this;
+
+#ifdef DIRECTX_ENABLE_XWMA
+ const uint32_t tag = GetFormatTag(mWaveFormat);
+ if (tag == WAVE_FORMAT_WMAUDIO2 || tag == WAVE_FORMAT_WMAUDIO3)
+ {
+ XAUDIO2_BUFFER_WMA wmaBuffer = {};
+ wmaBuffer.PacketCount = mSeekCount;
+ wmaBuffer.pDecodedPacketCumulativeBytes = mSeekTable;
+
+ hr = voice->SubmitSourceBuffer(&buffer, &wmaBuffer);
+ }
+ else
+ #endif // xWMA
+ {
+ hr = voice->SubmitSourceBuffer(&buffer, nullptr);
+ }
+ if (FAILED(hr))
+ {
+ DebugTrace("ERROR: SoundEffect failed (%08X) when submitting buffer:\n", static_cast(hr));
+ DebugTrace("\tFormat Tag %u, %u channels, %u-bit, %u Hz, %u bytes\n",
+ mWaveFormat->wFormatTag, mWaveFormat->nChannels, mWaveFormat->wBitsPerSample, mWaveFormat->nSamplesPerSec, mAudioBytes);
+ throw std::runtime_error("SubmitSourceBuffer");
+ }
+
+ InterlockedIncrement(&mOneShots);
+}
+
+
+//--------------------------------------------------------------------------------------
+// SoundEffect
+//--------------------------------------------------------------------------------------
+
+// Public constructors.
+_Use_decl_annotations_
+SoundEffect::SoundEffect(AudioEngine* engine, const wchar_t* waveFileName)
+ : pImpl(std::make_unique(engine))
+{
+ WAVData wavInfo;
+ std::unique_ptr wavData;
+ HRESULT hr = LoadWAVAudioFromFileEx(waveFileName, wavData, wavInfo);
+ if (FAILED(hr))
+ {
+ DebugTrace("ERROR: SoundEffect failed (%08X) to load from .wav file \"%ls\"\n",
+ static_cast(hr), waveFileName);
+ throw std::runtime_error("SoundEffect");
+ }
+
+#ifdef DIRECTX_ENABLE_SEEK_TABLES
+ hr = pImpl->Initialize(engine, wavData, wavInfo.wfx, wavInfo.startAudio, wavInfo.audioBytes,
+ wavInfo.seek, wavInfo.seekCount,
+ wavInfo.loopStart, wavInfo.loopLength);
+#else
+ hr = pImpl->Initialize(engine, wavData, wavInfo.wfx, wavInfo.startAudio, wavInfo.audioBytes,
+ wavInfo.loopStart, wavInfo.loopLength);
+#endif
+
+ if (FAILED(hr))
+ {
+ DebugTrace("ERROR: SoundEffect failed (%08X) to intialize from .wav file \"%ls\"\n",
+ static_cast(hr), waveFileName);
+ throw std::runtime_error("SoundEffect");
+ }
+}
+
+
+_Use_decl_annotations_
+SoundEffect::SoundEffect(AudioEngine* engine, std::unique_ptr& wavData,
+ const WAVEFORMATEX* wfx, const uint8_t* startAudio, size_t audioBytes)
+ : pImpl(std::make_unique(engine))
+{
+#ifdef DIRECTX_ENABLE_SEEK_TABLES
+ HRESULT hr = pImpl->Initialize(engine, wavData, wfx, startAudio, audioBytes, nullptr, 0, 0, 0);
+#else
+ HRESULT hr = pImpl->Initialize(engine, wavData, wfx, startAudio, audioBytes, 0, 0);
+#endif
+ if (FAILED(hr))
+ {
+ DebugTrace("ERROR: SoundEffect failed (%08X) to intialize\n", static_cast(hr));
+ throw std::runtime_error("SoundEffect");
+ }
+}
+
+
+_Use_decl_annotations_
+SoundEffect::SoundEffect(AudioEngine* engine, std::unique_ptr& wavData,
+ const WAVEFORMATEX* wfx, const uint8_t* startAudio, size_t audioBytes,
+ uint32_t loopStart, uint32_t loopLength)
+ : pImpl(std::make_unique(engine))
+{
+#ifdef DIRECTX_ENABLE_SEEK_TABLES
+ HRESULT hr = pImpl->Initialize(engine, wavData, wfx, startAudio, audioBytes, nullptr, 0, loopStart, loopLength);
+#else
+ HRESULT hr = pImpl->Initialize(engine, wavData, wfx, startAudio, audioBytes, loopStart, loopLength);
+#endif
+ if (FAILED(hr))
+ {
+ DebugTrace("ERROR: SoundEffect failed (%08X) to intialize\n", static_cast(hr));
+ throw std::runtime_error("SoundEffect");
+ }
+}
+
+
+#ifdef DIRECTX_ENABLE_SEEK_TABLES
+
+_Use_decl_annotations_
+SoundEffect::SoundEffect(AudioEngine* engine, std::unique_ptr& wavData,
+ const WAVEFORMATEX* wfx, const uint8_t* startAudio, size_t audioBytes,
+ const uint32_t* seekTable, size_t seekCount)
+{
+ HRESULT hr = pImpl->Initialize(engine, wavData, wfx, startAudio, audioBytes, seekTable, seekCount, 0, 0);
+ if (FAILED(hr))
+ {
+ DebugTrace("ERROR: SoundEffect failed (%08X) to intialize\n", static_cast(hr));
+ throw std::runtime_error("SoundEffect");
+ }
+}
+
+#endif
+
+
+SoundEffect::SoundEffect(SoundEffect&&) noexcept = default;
+SoundEffect& SoundEffect::operator= (SoundEffect&&) noexcept = default;
+SoundEffect::~SoundEffect() = default;
+
+
+// Public methods.
+void SoundEffect::Play()
+{
+ pImpl->Play(1.f, 0.f, 0.f);
+}
+
+
+void SoundEffect::Play(float volume, float pitch, float pan)
+{
+ pImpl->Play(volume, pitch, pan);
+}
+
+
+std::unique_ptr SoundEffect::CreateInstance(SOUND_EFFECT_INSTANCE_FLAGS flags)
+{
+ auto effect = new SoundEffectInstance(pImpl->mEngine, this, flags);
+ assert(effect != nullptr);
+ pImpl->mInstances.emplace_back(effect->GetVoiceNotify());
+ return std::unique_ptr(effect);
+}
+
+
+void SoundEffect::UnregisterInstance(_In_ IVoiceNotify* instance)
+{
+ auto it = std::find(pImpl->mInstances.begin(), pImpl->mInstances.end(), instance);
+ if (it == pImpl->mInstances.end())
+ return;
+
+ pImpl->mInstances.erase(it);
+}
+
+
+// Public accessors.
+bool SoundEffect::IsInUse() const noexcept
+{
+ return (pImpl->mOneShots > 0) || !pImpl->mInstances.empty();
+}
+
+
+size_t SoundEffect::GetSampleSizeInBytes() const noexcept
+{
+ return pImpl->mAudioBytes;
+}
+
+
+size_t SoundEffect::GetSampleDuration() const noexcept
+{
+ if (!pImpl->mWaveFormat || !pImpl->mWaveFormat->nChannels)
+ return 0;
+
+ switch (GetFormatTag(pImpl->mWaveFormat))
+ {
+ case WAVE_FORMAT_ADPCM:
+ {
+ auto adpcmFmt = reinterpret_cast(pImpl->mWaveFormat);
+
+ uint64_t duration = uint64_t(pImpl->mAudioBytes / adpcmFmt->wfx.nBlockAlign) * adpcmFmt->wSamplesPerBlock;
+ const unsigned int partial = pImpl->mAudioBytes % adpcmFmt->wfx.nBlockAlign;
+ if (partial)
+ {
+ if (partial >= (7u * adpcmFmt->wfx.nChannels))
+ duration += (uint64_t(partial) * 2 / uint64_t(adpcmFmt->wfx.nChannels - 12));
+ }
+ return static_cast(duration);
+ }
+
+ #ifdef DIRECTX_ENABLE_XWMA
+
+ case WAVE_FORMAT_WMAUDIO2:
+ case WAVE_FORMAT_WMAUDIO3:
+ if (pImpl->mSeekTable && pImpl->mSeekCount > 0)
+ {
+ return pImpl->mSeekTable[pImpl->mSeekCount - 1] / uint32_t(2 * pImpl->mWaveFormat->nChannels);
+ }
+ break;
+
+ #endif
+
+ #ifdef DIRECTX_ENABLE_XMA2
+
+ case WAVE_FORMAT_XMA2:
+ return reinterpret_cast(pImpl->mWaveFormat)->SamplesEncoded;
+
+ #endif
+
+ default:
+ if (pImpl->mWaveFormat->wBitsPerSample > 0)
+ {
+ return static_cast((uint64_t(pImpl->mAudioBytes) * 8)
+ / (uint64_t(pImpl->mWaveFormat->wBitsPerSample) * uint64_t(pImpl->mWaveFormat->nChannels)));
+ }
+ }
+
+ return 0;
+}
+
+
+size_t SoundEffect::GetSampleDurationMS() const noexcept
+{
+ if (!pImpl->mWaveFormat || !pImpl->mWaveFormat->nSamplesPerSec)
+ return 0;
+
+ const uint64_t samples = GetSampleDuration();
+ return static_cast((samples * 1000) / pImpl->mWaveFormat->nSamplesPerSec);
+}
+
+
+const WAVEFORMATEX* SoundEffect::GetFormat() const noexcept
+{
+ return pImpl->mWaveFormat;
+}
+
+
+#ifdef DIRECTX_ENABLE_XWMA
+
+bool SoundEffect::FillSubmitBuffer(_Out_ XAUDIO2_BUFFER& buffer, _Out_ XAUDIO2_BUFFER_WMA& wmaBuffer) const
+{
+ memset(&buffer, 0, sizeof(buffer));
+ memset(&wmaBuffer, 0, sizeof(wmaBuffer));
+
+ buffer.AudioBytes = pImpl->mAudioBytes;
+ buffer.pAudioData = pImpl->mStartAudio;
+ buffer.LoopBegin = pImpl->mLoopStart;
+ buffer.LoopLength = pImpl->mLoopLength;
+
+ const uint32_t tag = GetFormatTag(pImpl->mWaveFormat);
+ if (tag == WAVE_FORMAT_WMAUDIO2 || tag == WAVE_FORMAT_WMAUDIO3)
+ {
+ wmaBuffer.PacketCount = pImpl->mSeekCount;
+ wmaBuffer.pDecodedPacketCumulativeBytes = pImpl->mSeekTable;
+ return true;
+ }
+
+ return false;
+}
+
+#else // !xWMA
+
+void SoundEffect::FillSubmitBuffer(_Out_ XAUDIO2_BUFFER& buffer) const
+{
+ memset(&buffer, 0, sizeof(buffer));
+ buffer.AudioBytes = pImpl->mAudioBytes;
+ buffer.pAudioData = pImpl->mStartAudio;
+ buffer.LoopBegin = pImpl->mLoopStart;
+ buffer.LoopLength = pImpl->mLoopLength;
+}
+
+#endif
+
+
+//--------------------------------------------------------------------------------------
+// Adapters for /Zc:wchar_t- clients
+#if defined(_MSC_VER) && !defined(_NATIVE_WCHAR_T_DEFINED)
+
+_Use_decl_annotations_
+SoundEffect::SoundEffect(AudioEngine* engine, const __wchar_t* waveFileName) :
+ SoundEffect(engine, reinterpret_cast(waveFileName))
+{
+}
+
+#endif // !_NATIVE_WCHAR_T_DEFINED
diff --git a/Common/DirectXTK12/Audio/SoundEffectInstance.cpp b/Common/DirectXTK12/Audio/SoundEffectInstance.cpp
new file mode 100644
index 0000000..7022bcc
--- /dev/null
+++ b/Common/DirectXTK12/Audio/SoundEffectInstance.cpp
@@ -0,0 +1,337 @@
+//--------------------------------------------------------------------------------------
+// File: SoundEffectInstance.cpp
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+// http://go.microsoft.com/fwlink/?LinkId=248929
+// http://go.microsoft.com/fwlink/?LinkID=615561
+//--------------------------------------------------------------------------------------
+
+#include "pch.h"
+#include "SoundCommon.h"
+
+using namespace DirectX;
+
+
+//======================================================================================
+// SoundEffectInstance
+//======================================================================================
+
+// Internal object implementation class.
+class SoundEffectInstance::Impl : public IVoiceNotify
+{
+public:
+ Impl(_In_ AudioEngine* engine, _In_ SoundEffect* effect, SOUND_EFFECT_INSTANCE_FLAGS flags) :
+ mBase(),
+ mEffect(effect),
+ mWaveBank(nullptr),
+ mIndex(0),
+ mLooped(false)
+ {
+ assert(engine != nullptr);
+ engine->RegisterNotify(this, false);
+
+ assert(mEffect != nullptr);
+ mBase.Initialize(engine, effect->GetFormat(), flags);
+ }
+
+ Impl(_In_ AudioEngine* engine, _In_ WaveBank* waveBank, uint32_t index, SOUND_EFFECT_INSTANCE_FLAGS flags) :
+ mBase(),
+ mEffect(nullptr),
+ mWaveBank(waveBank),
+ mIndex(index),
+ mLooped(false)
+ {
+ assert(engine != nullptr);
+ engine->RegisterNotify(this, false);
+
+ char buff[64] = {};
+ auto wfx = reinterpret_cast(buff);
+ assert(mWaveBank != nullptr);
+ mBase.Initialize(engine, mWaveBank->GetFormat(index, wfx, sizeof(buff)), flags);
+ }
+
+ Impl(Impl&&) = default;
+ Impl& operator= (Impl&&) = default;
+
+ Impl(Impl const&) = delete;
+ Impl& operator= (Impl const&) = delete;
+
+ ~Impl() override
+ {
+ mBase.DestroyVoice();
+
+ if (mBase.engine)
+ {
+ mBase.engine->UnregisterNotify(this, false, false);
+ mBase.engine = nullptr;
+ }
+ }
+
+ void Play(bool loop);
+
+ // IVoiceNotify
+ void __cdecl OnBufferEnd() override
+ {
+ // We don't register for this notification for SoundEffectInstances, so this should not be invoked
+ assert(false);
+ }
+
+ void __cdecl OnCriticalError() override
+ {
+ mBase.OnCriticalError();
+ }
+
+ void __cdecl OnReset() override
+ {
+ mBase.OnReset();
+ }
+
+ void __cdecl OnUpdate() override
+ {
+ // We do not register for update notification
+ assert(false);
+ }
+
+ void __cdecl OnDestroyEngine() noexcept override
+ {
+ mBase.OnDestroy();
+ }
+
+ void __cdecl OnTrim() override
+ {
+ mBase.OnTrim();
+ }
+
+ void __cdecl GatherStatistics(AudioStatistics& stats) const noexcept override
+ {
+ mBase.GatherStatistics(stats);
+ }
+
+ void __cdecl OnDestroyParent() noexcept override
+ {
+ mBase.OnDestroy();
+ mWaveBank = nullptr;
+ mEffect = nullptr;
+ }
+
+ SoundEffectInstanceBase mBase;
+ SoundEffect* mEffect;
+ WaveBank* mWaveBank;
+ uint32_t mIndex;
+ bool mLooped;
+};
+
+
+void SoundEffectInstance::Impl::Play(bool loop)
+{
+ if (!mBase.voice)
+ {
+ if (mWaveBank)
+ {
+ char buff[64] = {};
+ auto wfx = reinterpret_cast(buff);
+ mBase.AllocateVoice(mWaveBank->GetFormat(mIndex, wfx, sizeof(buff)));
+ }
+ else
+ {
+ assert(mEffect != nullptr);
+ mBase.AllocateVoice(mEffect->GetFormat());
+ }
+ }
+
+ if (!mBase.Play())
+ return;
+
+ // Submit audio data for STOPPED -> PLAYING state transition
+ XAUDIO2_BUFFER buffer = {};
+
+#ifdef DIRECTX_ENABLE_XWMA
+
+ bool iswma = false;
+ XAUDIO2_BUFFER_WMA wmaBuffer = {};
+ if (mWaveBank)
+ {
+ iswma = mWaveBank->FillSubmitBuffer(mIndex, buffer, wmaBuffer);
+ }
+ else
+ {
+ assert(mEffect != nullptr);
+ iswma = mEffect->FillSubmitBuffer(buffer, wmaBuffer);
+ }
+
+#else // !xWMA
+
+ if (mWaveBank)
+ {
+ mWaveBank->FillSubmitBuffer(mIndex, buffer);
+ }
+ else
+ {
+ assert(mEffect != nullptr);
+ mEffect->FillSubmitBuffer(buffer);
+ }
+
+#endif
+
+ buffer.Flags = XAUDIO2_END_OF_STREAM;
+ if (loop)
+ {
+ mLooped = true;
+ buffer.LoopCount = XAUDIO2_LOOP_INFINITE;
+ }
+ else
+ {
+ mLooped = false;
+ buffer.LoopCount = buffer.LoopBegin = buffer.LoopLength = 0;
+ }
+ buffer.pContext = nullptr;
+
+ HRESULT hr;
+#ifdef DIRECTX_ENABLE_XWMA
+ if (iswma)
+ {
+ hr = mBase.voice->SubmitSourceBuffer(&buffer, &wmaBuffer);
+ }
+ else
+ #endif
+ {
+ hr = mBase.voice->SubmitSourceBuffer(&buffer, nullptr);
+ }
+
+ if (FAILED(hr))
+ {
+ #ifdef _DEBUG
+ DebugTrace("ERROR: SoundEffectInstance failed (%08X) when submitting buffer:\n", static_cast(hr));
+
+ char buff[64] = {};
+ auto wfx = (mWaveBank) ? mWaveBank->GetFormat(mIndex, reinterpret_cast(buff), sizeof(buff))
+ : mEffect->GetFormat();
+
+ const size_t length = (mWaveBank) ? mWaveBank->GetSampleSizeInBytes(mIndex) : mEffect->GetSampleSizeInBytes();
+
+ DebugTrace("\tFormat Tag %u, %u channels, %u-bit, %u Hz, %zu bytes\n",
+ wfx->wFormatTag, wfx->nChannels, wfx->wBitsPerSample, wfx->nSamplesPerSec, length);
+ #endif
+ mBase.Stop(true, mLooped);
+ throw std::runtime_error("SubmitSourceBuffer");
+ }
+}
+
+
+//--------------------------------------------------------------------------------------
+// SoundEffectInstance
+//--------------------------------------------------------------------------------------
+
+// Private constructors
+_Use_decl_annotations_
+SoundEffectInstance::SoundEffectInstance(AudioEngine* engine, SoundEffect* effect, SOUND_EFFECT_INSTANCE_FLAGS flags) :
+ pImpl(std::make_unique(engine, effect, flags))
+{
+}
+
+_Use_decl_annotations_
+SoundEffectInstance::SoundEffectInstance(AudioEngine* engine, WaveBank* waveBank, unsigned int index, SOUND_EFFECT_INSTANCE_FLAGS flags) :
+ pImpl(std::make_unique(engine, waveBank, index, flags))
+{
+}
+
+
+// Move ctor/operator.
+SoundEffectInstance::SoundEffectInstance(SoundEffectInstance&&) noexcept = default;
+SoundEffectInstance& SoundEffectInstance::operator= (SoundEffectInstance&&) noexcept = default;
+
+
+// Public destructor.
+SoundEffectInstance::~SoundEffectInstance()
+{
+ if (pImpl)
+ {
+ if (pImpl->mWaveBank)
+ {
+ pImpl->mWaveBank->UnregisterInstance(pImpl.get());
+ pImpl->mWaveBank = nullptr;
+ }
+
+ if (pImpl->mEffect)
+ {
+ pImpl->mEffect->UnregisterInstance(pImpl.get());
+ pImpl->mEffect = nullptr;
+ }
+ }
+}
+
+
+// Public methods.
+void SoundEffectInstance::Play(bool loop)
+{
+ pImpl->Play(loop);
+}
+
+
+void SoundEffectInstance::Stop(bool immediate) noexcept
+{
+ pImpl->mBase.Stop(immediate, pImpl->mLooped);
+}
+
+
+void SoundEffectInstance::Pause() noexcept
+{
+ pImpl->mBase.Pause();
+}
+
+
+void SoundEffectInstance::Resume()
+{
+ pImpl->mBase.Resume();
+}
+
+
+void SoundEffectInstance::SetVolume(float volume)
+{
+ pImpl->mBase.SetVolume(volume);
+}
+
+
+void SoundEffectInstance::SetPitch(float pitch)
+{
+ pImpl->mBase.SetPitch(pitch);
+}
+
+
+void SoundEffectInstance::SetPan(float pan)
+{
+ pImpl->mBase.SetPan(pan);
+}
+
+
+void SoundEffectInstance::Apply3D(const X3DAUDIO_LISTENER& listener, const X3DAUDIO_EMITTER& emitter, bool rhcoords)
+{
+ pImpl->mBase.Apply3D(listener, emitter, rhcoords);
+}
+
+
+// Public accessors.
+bool SoundEffectInstance::IsLooped() const noexcept
+{
+ return pImpl->mLooped;
+}
+
+
+SoundState SoundEffectInstance::GetState() noexcept
+{
+ return pImpl->mBase.GetState(true);
+}
+
+
+unsigned int SoundEffectInstance::GetChannelCount() const noexcept
+{
+ return pImpl->mBase.GetChannelCount();
+}
+
+
+IVoiceNotify* SoundEffectInstance::GetVoiceNotify() const noexcept
+{
+ return pImpl.get();
+}
diff --git a/Common/DirectXTK12/Audio/SoundStreamInstance.cpp b/Common/DirectXTK12/Audio/SoundStreamInstance.cpp
new file mode 100644
index 0000000..aef9a83
--- /dev/null
+++ b/Common/DirectXTK12/Audio/SoundStreamInstance.cpp
@@ -0,0 +1,860 @@
+//--------------------------------------------------------------------------------------
+// File: SoundStreamInstance.cpp
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+// http://go.microsoft.com/fwlink/?LinkId=248929
+// http://go.microsoft.com/fwlink/?LinkID=615561
+//--------------------------------------------------------------------------------------
+
+#include "pch.h"
+#include "DirectXHelpers.h"
+#include "WaveBankReader.h"
+#include "PlatformHelpers.h"
+#include "SoundCommon.h"
+
+#if (defined(_XBOX_ONE) && defined(_TITLE)) || defined(_GAMING_XBOX)
+#ifdef __clang__
+#pragma clang diagnostic ignored "-Wnonportable-system-include-path"
+#endif
+
+#include
+#include
+#endif
+
+using namespace DirectX;
+
+#ifdef __clang__
+#pragma clang diagnostic ignored "-Wcovered-switch-default"
+#endif
+
+#pragma warning(disable : 4061 4062)
+
+//#define VERBOSE_TRACE
+
+#ifdef VERBOSE_TRACE
+#pragma message("NOTE: Verbose tracing enabled")
+#endif
+
+namespace
+{
+ constexpr size_t DVD_SECTOR_SIZE = 2048;
+ constexpr size_t ADVANCED_FORMAT_SECTOR_SIZE = 4096;
+ constexpr size_t MAX_BUFFER_COUNT = 3;
+
+#ifdef DIRECTX_ENABLE_SEEK_TABLES
+ constexpr size_t MAX_STREAMING_SEEK_PACKETS = 2048;
+#endif
+
+#ifdef DIRECTX_ENABLE_XMA2
+ constexpr size_t XMA2_64KBLOCKINBYTES = 65536;
+
+ struct apu_deleter { void operator()(void* p) noexcept { if (p) ApuFree(p); } };
+#endif
+
+ size_t ComputeAsyncPacketSize(_In_ const WAVEFORMATEX* wfx, uint32_t tag, uint32_t alignment)
+ {
+ if (!wfx)
+ return 0;
+
+ size_t buffer = size_t(wfx->nAvgBytesPerSec) * 2u;
+
+ #ifdef DIRECTX_ENABLE_XMA2
+ if (tag == WAVE_FORMAT_XMA2)
+ {
+ buffer = AlignUp(buffer, XMA2_64KBLOCKINBYTES);
+ buffer = std::max(XMA2_64KBLOCKINBYTES, buffer);
+ return buffer;
+ }
+ #else
+ UNREFERENCED_PARAMETER(tag);
+ #endif
+
+ buffer = AlignUp(buffer, size_t(alignment) * 2);
+ buffer = std::max(65536u, buffer);
+ return buffer;
+ }
+}
+
+
+//======================================================================================
+// SoundStreamInstance
+//======================================================================================
+
+// Internal object implementation class.
+class SoundStreamInstance::Impl : public IVoiceNotify
+{
+public:
+ Impl(_In_ AudioEngine* engine,
+ WaveBank* waveBank,
+ uint32_t index,
+ SOUND_EFFECT_INSTANCE_FLAGS flags) noexcept(false) :
+ mBase(),
+ mWaveBank(waveBank),
+ mIndex(index),
+ mPlaying(false),
+ mLooped(false),
+ mEndStream(false),
+ mPrefetch(false),
+ mSitching(false),
+ mPackets{},
+ mCurrentDiskReadBuffer(0),
+ mCurrentPlayBuffer(0),
+ mBlockAlign(0),
+ mAsyncAlign(DVD_SECTOR_SIZE),
+ mCurrentPosition(0),
+ mOffsetBytes(0),
+ mLengthInBytes(0),
+ mPacketSize(0),
+ mTotalSize(0)
+ #ifdef DIRECTX_ENABLE_SEEK_TABLES
+ , mSeekCount(0),
+ mSeekTable(nullptr),
+ mSeekTableCopy{}
+ #endif
+ {
+ assert(engine != nullptr);
+ engine->RegisterNotify(this, true);
+
+ char buff[64] = {};
+ auto wfx = reinterpret_cast(buff);
+ assert(mWaveBank != nullptr);
+ mBase.Initialize(engine, mWaveBank->GetFormat(index, wfx, sizeof(buff)), flags);
+
+ WaveBankReader::Metadata metadata = {};
+ std::ignore = mWaveBank->GetPrivateData(index, &metadata, sizeof(metadata));
+
+ mOffsetBytes = metadata.offsetBytes;
+ mLengthInBytes = metadata.lengthBytes;
+ mAsyncAlign = mWaveBank->IsAdvancedFormat() ? ADVANCED_FORMAT_SECTOR_SIZE : DVD_SECTOR_SIZE;
+
+ #ifdef DIRECTX_ENABLE_SEEK_TABLES
+ WaveBankSeekData seekData = {};
+ std::ignore = mWaveBank->GetPrivateData(index, &seekData, sizeof(seekData));
+ if (seekData.tag == WAVE_FORMAT_WMAUDIO2 || seekData.tag == WAVE_FORMAT_WMAUDIO3)
+ {
+ mSeekCount = seekData.seekCount;
+ mSeekTable = seekData.seekTable;
+ }
+ #endif
+
+ mBufferEnd.reset(CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE));
+ mBufferRead.reset(CreateEventEx(nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE));
+ if (!mBufferEnd || !mBufferRead)
+ {
+ throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "CreateEventEx");
+ }
+
+ ThrowIfFailed(AllocateStreamingBuffers(wfx));
+
+ #ifdef VERBOSE_TRACE
+ DebugTrace("INFO (Streaming): packet size %zu, play length %zu\n", mPacketSize, mLengthInBytes);
+ #endif
+
+ mPrefetch = true;
+ ThrowIfFailed(ReadBuffers());
+ }
+
+ virtual ~Impl() override
+ {
+ mBase.DestroyVoice();
+
+ if (mWaveBank && mWaveBank->GetAsyncHandle())
+ {
+ for (size_t j = 0; j < MAX_BUFFER_COUNT; ++j)
+ {
+ std::ignore = CancelIoEx(mWaveBank->GetAsyncHandle(), &mPackets[j].request);
+ }
+ }
+
+ if (mBase.engine)
+ {
+ mBase.engine->UnregisterNotify(this, false, true);
+ mBase.engine = nullptr;
+ }
+
+ for (size_t j = 0; j < MAX_BUFFER_COUNT; ++j)
+ {
+ mPackets[j] = {};
+ }
+ mPacketSize = 0;
+ }
+
+ Impl(Impl&&) = default;
+ Impl& operator= (Impl&&) = default;
+
+ Impl(Impl const&) = delete;
+ Impl& operator= (Impl const&) = delete;
+
+ void Play(bool loop)
+ {
+ if (!mBase.voice)
+ {
+ if (!mWaveBank)
+ return;
+
+ char buff[64] = {};
+ auto wfx = reinterpret_cast(buff);
+ mBase.AllocateVoice(mWaveBank->GetFormat(mIndex, wfx, sizeof(buff)));
+ }
+
+ if (!mBase.Play())
+ return;
+
+ mLooped = loop;
+ mEndStream = false;
+
+ if (!mPrefetch)
+ {
+ mCurrentPosition = 0;
+ }
+
+ ThrowIfFailed(PlayBuffers());
+ }
+
+ // IVoiceNotify
+ virtual void __cdecl OnBufferEnd() override
+ {
+ // Not used
+ }
+
+ virtual void __cdecl OnCriticalError() override
+ {
+ mBase.OnCriticalError();
+ }
+
+ virtual void __cdecl OnReset() override
+ {
+ mBase.OnReset();
+ }
+
+ virtual void __cdecl OnUpdate() override
+ {
+ if (!mPlaying)
+ return;
+
+ HANDLE events[] = { mBufferRead.get(), mBufferEnd.get() };
+ switch (WaitForMultipleObjectsEx(static_cast(std::size(events)), events, FALSE, 0, FALSE))
+ {
+ case WAIT_TIMEOUT:
+ break;
+
+ case WAIT_OBJECT_0: // Read completed
+ #ifdef VERBOSE_TRACE
+ DebugTrace("INFO (Streaming): Playing... (readpos %zu) [", mCurrentPosition);
+ for (uint32_t k = 0; k < MAX_BUFFER_COUNT; ++k)
+ {
+ DebugTrace("%ls ", s_debugState[static_cast(mPackets[k].state)]);
+ }
+ DebugTrace("]\n");
+ #endif
+ mPrefetch = false;
+ ThrowIfFailed(PlayBuffers());
+ break;
+
+ case (WAIT_OBJECT_0 + 1): // Play completed
+ #ifdef VERBOSE_TRACE
+ DebugTrace("INFO (Streaming): Reading... (readpos %zu) [", mCurrentPosition);
+ for (uint32_t k = 0; k < MAX_BUFFER_COUNT; ++k)
+ {
+ DebugTrace("%ls ", s_debugState[static_cast(mPackets[k].state)]);
+ }
+ DebugTrace("]\n");
+ #endif
+ ThrowIfFailed(ReadBuffers());
+ break;
+
+ case WAIT_FAILED:
+ throw std::system_error(std::error_code(static_cast(GetLastError()), std::system_category()), "WaitForMultipleObjectsEx");
+ }
+ }
+
+ virtual void __cdecl OnDestroyEngine() noexcept override
+ {
+ mBase.OnDestroy();
+ }
+
+ virtual void __cdecl OnTrim() override
+ {
+ mBase.OnTrim();
+ }
+
+ virtual void __cdecl GatherStatistics(AudioStatistics& stats) const noexcept override
+ {
+ mBase.GatherStatistics(stats);
+
+ stats.streamingBytes += mPacketSize * MAX_BUFFER_COUNT;
+ }
+
+ virtual void __cdecl OnDestroyParent() noexcept override
+ {
+ mBase.OnDestroy();
+ mWaveBank = nullptr;
+ }
+
+ SoundEffectInstanceBase mBase;
+ WaveBank* mWaveBank;
+ uint32_t mIndex;
+ bool mPlaying;
+ bool mLooped;
+ bool mEndStream;
+ bool mPrefetch;
+ bool mSitching;
+
+ ScopedHandle mBufferEnd;
+ ScopedHandle mBufferRead;
+
+ enum class State : uint32_t
+ {
+ FREE = 0,
+ PENDING,
+ READY,
+ PLAYING,
+ };
+
+#ifdef VERBOSE_TRACE
+ static const wchar_t* s_debugState[4];
+#endif
+
+ struct BufferNotify : public IVoiceNotify
+ {
+ BufferNotify() : mParent(nullptr), mIndex(0) {}
+
+ void Set(SoundStreamInstance::Impl* parent, size_t index) noexcept(true) { mParent = parent; mIndex = index; }
+
+ void __cdecl OnBufferEnd() override
+ {
+ assert(mParent != nullptr);
+ mParent->mPackets[mIndex].state = State::FREE;
+ SetEvent(mParent->mBufferEnd.get());
+ }
+
+ void __cdecl OnCriticalError() override { assert(mParent != nullptr); mParent->OnCriticalError(); }
+ void __cdecl OnReset() override { assert(mParent != nullptr); mParent->OnReset(); }
+ void __cdecl OnUpdate() override { assert(mParent != nullptr); mParent->OnUpdate(); }
+ void __cdecl OnDestroyEngine() noexcept override { assert(mParent != nullptr); mParent->OnDestroyEngine(); }
+ void __cdecl OnTrim() override { assert(mParent != nullptr); mParent->OnTrim(); }
+ void __cdecl GatherStatistics(AudioStatistics& stats) const override { assert(mParent != nullptr); mParent->GatherStatistics(stats); }
+ void __cdecl OnDestroyParent() noexcept override { assert(mParent != nullptr); mParent->OnDestroyParent(); }
+
+ private:
+ SoundStreamInstance::Impl* mParent;
+ size_t mIndex;
+ };
+
+ struct Packets
+ {
+ State state;
+ uint8_t* buffer;
+ uint8_t* stitchBuffer;
+ uint32_t valid;
+ uint32_t audioBytes;
+ uint32_t startPosition;
+ OVERLAPPED request;
+ BufferNotify notify;
+
+ Packets() :
+ state(State::FREE),
+ buffer(nullptr),
+ stitchBuffer(nullptr),
+ valid(0),
+ audioBytes(0),
+ startPosition(0),
+ request{},
+ notify{} {}
+ };
+
+ Packets mPackets[MAX_BUFFER_COUNT];
+
+private:
+ uint32_t mCurrentDiskReadBuffer;
+ uint32_t mCurrentPlayBuffer;
+ uint32_t mBlockAlign;
+ uint32_t mAsyncAlign;
+ size_t mCurrentPosition;
+ size_t mOffsetBytes;
+ size_t mLengthInBytes;
+
+ size_t mPacketSize;
+ size_t mTotalSize;
+ std::unique_ptr mStreamBuffer;
+
+#ifdef DIRECTX_ENABLE_SEEK_TABLES
+ uint32_t mSeekCount;
+ const uint32_t* mSeekTable;
+ uint32_t mSeekTableCopy[MAX_STREAMING_SEEK_PACKETS];
+#endif
+
+#ifdef DIRECTX_ENABLE_XMA2
+ std::unique_ptr mXMAMemory;
+#endif
+
+ HRESULT AllocateStreamingBuffers(const WAVEFORMATEX* wfx) noexcept;
+ HRESULT ReadBuffers() noexcept;
+ HRESULT PlayBuffers() noexcept;
+};
+
+
+HRESULT SoundStreamInstance::Impl::AllocateStreamingBuffers(const WAVEFORMATEX* wfx) noexcept
+{
+ if (!wfx)
+ return E_INVALIDARG;
+
+ const uint32_t tag = GetFormatTag(wfx);
+
+ size_t packetSize = ComputeAsyncPacketSize(wfx, tag, mAsyncAlign);
+ if (!packetSize)
+ return E_UNEXPECTED;
+
+ uint64_t totalSize = uint64_t(packetSize) * uint64_t(MAX_BUFFER_COUNT);
+ if (totalSize > UINT32_MAX)
+ return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);
+
+ mPacketSize = packetSize;
+ mBlockAlign = wfx->nBlockAlign;
+ mSitching = false;
+
+ size_t stitchSize = 0;
+ if ((packetSize % wfx->nBlockAlign) != 0)
+ {
+ mSitching = true;
+
+ stitchSize = AlignUp(wfx->nBlockAlign, mAsyncAlign);
+ totalSize += uint64_t(stitchSize) * uint64_t(MAX_BUFFER_COUNT);
+ if (totalSize > UINT32_MAX)
+ return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);
+ }
+
+#ifdef DIRECTX_ENABLE_XMA2
+ if ((mTotalSize < totalSize) || (tag == WAVE_FORMAT_XMA2 && !mXMAMemory) || (tag != WAVE_FORMAT_XMA2 && !mStreamBuffer))
+ #else
+ if (mTotalSize < totalSize)
+ #endif
+ {
+ mStreamBuffer.reset();
+ #ifdef DIRECTX_ENABLE_XMA2
+ mXMAMemory.reset();
+ if (tag == WAVE_FORMAT_XMA2)
+ {
+ void* xmaMemory = nullptr;
+ HRESULT hr = ApuAlloc(&xmaMemory, nullptr, static_cast(totalSize), SHAPE_XMA_INPUT_BUFFER_ALIGNMENT);
+ if (FAILED(hr))
+ {
+ DebugTrace("ERROR: ApuAlloc failed (%llu bytes). Did you allocate a large enough heap with ApuCreateHeap for all your XMA wave data?\n", totalSize);
+ return hr;
+ }
+ mXMAMemory.reset(static_cast(xmaMemory));
+ }
+ else
+ #endif
+ {
+ mStreamBuffer.reset(reinterpret_cast(
+ VirtualAlloc(nullptr, static_cast(totalSize), MEM_COMMIT, PAGE_READWRITE)
+ ));
+
+ if (!mStreamBuffer)
+ {
+ DebugTrace("ERROR: Failed allocating %llu bytes for SoundStreamInstance\n", totalSize);
+ mPacketSize = 0;
+ totalSize = 0;
+ return E_OUTOFMEMORY;
+ }
+ }
+
+ mTotalSize = static_cast(totalSize);
+
+ #ifdef DIRECTX_ENABLE_XMA2
+ uint8_t* ptr = (tag == WAVE_FORMAT_XMA2) ? mXMAMemory.get() : mStreamBuffer.get();
+ #else
+ uint8_t* ptr = mStreamBuffer.get();
+ #endif
+ for (size_t j = 0; j < MAX_BUFFER_COUNT; ++j)
+ {
+ mPackets[j].buffer = ptr;
+ mPackets[j].stitchBuffer = nullptr;
+ mPackets[j].request.hEvent = mBufferRead.get();
+ mPackets[j].notify.Set(this, j);
+ ptr += packetSize;
+ }
+
+ if (stitchSize > 0)
+ {
+ for (size_t j = 0; j < MAX_BUFFER_COUNT; ++j)
+ {
+ mPackets[j].stitchBuffer = ptr;
+ ptr += stitchSize;
+ }
+ }
+ }
+
+ return S_OK;
+}
+
+
+HRESULT SoundStreamInstance::Impl::ReadBuffers() noexcept
+{
+ if (mCurrentPosition >= mLengthInBytes)
+ {
+ if (!mLooped)
+ {
+ mEndStream = true;
+ return S_FALSE;
+ }
+
+ #ifdef VERBOSE_TRACE
+ DebugTrace("INFO (Streaming): Loop restart\n");
+ #endif
+
+ mCurrentPosition = 0;
+ }
+
+ HANDLE async = mWaveBank->GetAsyncHandle();
+ if (!async)
+ return E_POINTER;
+
+ const uint32_t readBuffer = mCurrentDiskReadBuffer;
+ for (uint32_t j = 0; j < MAX_BUFFER_COUNT; ++j)
+ {
+ uint32_t entry = (j + readBuffer) % uint32_t(MAX_BUFFER_COUNT);
+ if (mPackets[entry].state == State::FREE)
+ {
+ if (mCurrentPosition < mLengthInBytes)
+ {
+ auto const cbValid = static_cast(std::min(mPacketSize, mLengthInBytes - mCurrentPosition));
+
+ mPackets[entry].valid = cbValid;
+ mPackets[entry].audioBytes = 0;
+ mPackets[entry].startPosition = static_cast(mCurrentPosition);
+ mPackets[entry].request.Offset = static_cast(mOffsetBytes + mCurrentPosition);
+
+ if (!ReadFile(async, mPackets[entry].buffer, uint32_t(mPacketSize), nullptr, &mPackets[entry].request))
+ {
+ const DWORD error = GetLastError();
+ if (error != ERROR_IO_PENDING)
+ {
+ #ifdef _DEBUG
+ if (error == ERROR_INVALID_PARAMETER)
+ {
+ // May be due to Advanced Format (4Kn) vs. DVD sector size. See the xwbtool -af switch.
+ OutputDebugStringA("ERROR: non-buffered async I/O failed: check disk sector size vs. streaming wave bank alignment!\n");
+ }
+ #endif
+ return HRESULT_FROM_WIN32(error);
+ }
+ }
+
+ mCurrentPosition += cbValid;
+
+ mCurrentDiskReadBuffer = (entry + 1) % uint32_t(MAX_BUFFER_COUNT);
+
+ mPackets[entry].state = State::PENDING;
+
+ if ((cbValid < mPacketSize) && mLooped)
+ {
+ #ifdef VERBOSE_TRACE
+ DebugTrace("INFO (Streaming): Loop restart\n");
+ #endif
+ mCurrentPosition = 0;
+ }
+ }
+ }
+ }
+
+ return S_OK;
+}
+
+
+HRESULT SoundStreamInstance::Impl::PlayBuffers() noexcept
+{
+ HANDLE async = mWaveBank->GetAsyncHandle();
+ if (!async)
+ return E_POINTER;
+
+ for (uint32_t j = 0; j < MAX_BUFFER_COUNT; ++j)
+ {
+ if (mPackets[j].state == State::PENDING)
+ {
+ DWORD cb = 0;
+ #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
+ const BOOL result = GetOverlappedResultEx(async, &mPackets[j].request, &cb, 0, FALSE);
+ #else
+ const BOOL result = GetOverlappedResult(async, &mPackets[j].request, &cb, FALSE);
+ #endif
+ if (result)
+ {
+ mPackets[j].state = State::READY;
+ }
+ else
+ {
+ const DWORD error = GetLastError();
+ if (error != ERROR_IO_INCOMPLETE)
+ {
+ ThrowIfFailed(HRESULT_FROM_WIN32(error));
+ }
+ }
+ }
+ }
+
+ if (!mBase.voice || !mPlaying)
+ return S_FALSE;
+
+ for (uint32_t j = 0; j < MAX_BUFFER_COUNT; ++j)
+ {
+ if (mPackets[mCurrentPlayBuffer].state != State::READY)
+ break;
+
+ const uint8_t* ptr = mPackets[mCurrentPlayBuffer].buffer;
+ uint32_t valid = mPackets[mCurrentPlayBuffer].valid;
+
+ bool endstream = false;
+ if (valid < mPacketSize)
+ {
+ endstream = true;
+ #ifdef VERBOSE_TRACE
+ DebugTrace("INFO (Streaming): End of stream (%u of %zu bytes)\n", mPackets[mCurrentPlayBuffer].valid, mPacketSize);
+ #endif
+ }
+
+ uint32_t thisFrameStitch = 0;
+ if (mSitching)
+ {
+ // Compute how many left-over bytes at the end of the previous packet (if any, they form the head of a partial block).
+ uint32_t prevFrameStitch = (mPackets[mCurrentPlayBuffer].startPosition % mBlockAlign);
+
+ if (prevFrameStitch > 0)
+ {
+ auto buffer = mPackets[mCurrentPlayBuffer].stitchBuffer;
+
+ // Compute how many bytes at the start of our current packet are the tail of the partial block.
+ thisFrameStitch = mBlockAlign - prevFrameStitch;
+
+ const uint32_t k = (mCurrentPlayBuffer + MAX_BUFFER_COUNT - 1) % MAX_BUFFER_COUNT;
+ if (mPackets[k].state == State::READY || mPackets[k].state == State::PLAYING)
+ {
+ // Compute how many bytes at the start of the previous packet were the tail of the previous stitch block.
+ uint32_t prevFrameStitchOffset = (mPackets[k].startPosition % mBlockAlign);
+ prevFrameStitchOffset = (prevFrameStitchOffset > 0) ? (mBlockAlign - prevFrameStitchOffset) : 0u;
+
+ // Point to the start of the partial block's head in the previous packet.
+ const auto *prevBuffer = mPackets[k].buffer + prevFrameStitchOffset + mPackets[k].audioBytes;
+
+ // Merge the the head partial block in the previous packet with the tail partial block at the start of our packet.
+ memcpy(buffer, prevBuffer, prevFrameStitch);
+ memcpy(buffer + prevFrameStitch, ptr, thisFrameStitch);
+
+ // Submit stitch packet (only need to get notified if we aren't submitting another packet for this buffer).
+ XAUDIO2_BUFFER buf = {};
+ buf.AudioBytes = mBlockAlign;
+ buf.pAudioData = buffer;
+
+ if (endstream && (valid <= thisFrameStitch))
+ {
+ buf.Flags = XAUDIO2_END_OF_STREAM;
+ buf.pContext = &mPackets[mCurrentPlayBuffer].notify;
+ }
+ #ifdef VERBOSE_TRACE
+ DebugTrace("INFO (Streaming): Stitch packet (%u + %u = %u)\n", prevFrameStitch, thisFrameStitch, mBlockAlign);
+ #endif
+ #ifdef DIRECTX_ENABLE_XWMA
+ if (mSeekCount > 0)
+ {
+ XAUDIO2_BUFFER_WMA wmaBuf = {};
+ wmaBuf.pDecodedPacketCumulativeBytes = mSeekTableCopy;
+ wmaBuf.PacketCount = 1;
+
+ const uint32_t seekOffset = (mPackets[k].startPosition + prevFrameStitchOffset + mPackets[k].audioBytes) / mBlockAlign;
+ assert(seekOffset > 0);
+ mSeekTableCopy[0] = mSeekTable[seekOffset] - mSeekTable[seekOffset - 1];
+
+ ThrowIfFailed(mBase.voice->SubmitSourceBuffer(&buf, &wmaBuf));
+ }
+ else
+ #endif // XWMA
+ {
+ ThrowIfFailed(mBase.voice->SubmitSourceBuffer(&buf));
+ }
+ }
+
+ ptr += thisFrameStitch;
+ }
+
+ // Compute valid audio bytes in our current packet.
+ valid = ((valid - thisFrameStitch) / mBlockAlign) * mBlockAlign;
+ }
+
+ if (valid > 0)
+ {
+ // Record the audioBytes we actually submitted...
+ mPackets[mCurrentPlayBuffer].audioBytes = valid;
+
+ XAUDIO2_BUFFER buf = {};
+ buf.Flags = (endstream) ? XAUDIO2_END_OF_STREAM : 0;
+ buf.AudioBytes = valid;
+ buf.pAudioData = ptr;
+ buf.pContext = &mPackets[mCurrentPlayBuffer].notify;
+
+ #ifdef DIRECTX_ENABLE_XWMA
+ if (mSeekCount > 0)
+ {
+ XAUDIO2_BUFFER_WMA wmaBuf = {};
+
+ wmaBuf.PacketCount = valid / mBlockAlign;
+
+ const uint32_t seekOffset = mPackets[mCurrentPlayBuffer].startPosition / mBlockAlign;
+ if (seekOffset > MAX_STREAMING_SEEK_PACKETS)
+ {
+ DebugTrace("ERROR: xWMA packet seek count exceeds %zu\n", MAX_STREAMING_SEEK_PACKETS);
+ return E_FAIL;
+ }
+ else if (seekOffset > 0)
+ {
+ for (uint32_t i = 0; i < wmaBuf.PacketCount; ++i)
+ {
+ mSeekTableCopy[i] = mSeekTable[i + seekOffset] - mSeekTable[seekOffset - 1];
+ }
+
+ wmaBuf.pDecodedPacketCumulativeBytes = mSeekTableCopy;
+ }
+ else
+ {
+ wmaBuf.pDecodedPacketCumulativeBytes = mSeekTable;
+ }
+
+ ThrowIfFailed(mBase.voice->SubmitSourceBuffer(&buf, &wmaBuf));
+ }
+ else
+ #endif // xWMA
+ {
+ ThrowIfFailed(mBase.voice->SubmitSourceBuffer(&buf));
+ }
+ }
+
+ mPackets[mCurrentPlayBuffer].state = State::PLAYING;
+ mCurrentPlayBuffer = (mCurrentPlayBuffer + 1) % uint32_t(MAX_BUFFER_COUNT);
+ }
+
+ return S_OK;
+}
+
+#ifdef VERBOSE_TRACE
+const wchar_t* SoundStreamInstance::Impl::s_debugState[4] =
+{
+ L"FREE",
+ L"PENDING",
+ L"READY",
+ L"PLAYING"
+};
+#endif
+
+
+//--------------------------------------------------------------------------------------
+// SoundStreamInstance
+//--------------------------------------------------------------------------------------
+
+// Private constructors
+_Use_decl_annotations_
+SoundStreamInstance::SoundStreamInstance(AudioEngine* engine, WaveBank* waveBank, unsigned int index, SOUND_EFFECT_INSTANCE_FLAGS flags) :
+ pImpl(std::make_unique(engine, waveBank, index, flags))
+{
+}
+
+
+// Move ctor/operator.
+SoundStreamInstance::SoundStreamInstance(SoundStreamInstance&&) noexcept = default;
+SoundStreamInstance& SoundStreamInstance::operator= (SoundStreamInstance&&) noexcept = default;
+
+
+// Public destructor.
+SoundStreamInstance::~SoundStreamInstance()
+{
+ if (pImpl)
+ {
+ if (pImpl->mWaveBank)
+ {
+ pImpl->mWaveBank->UnregisterInstance(pImpl.get());
+ pImpl->mWaveBank = nullptr;
+ }
+ }
+}
+
+
+// Public methods.
+void SoundStreamInstance::Play(bool loop)
+{
+ pImpl->Play(loop);
+ pImpl->mPlaying = true;
+}
+
+
+void SoundStreamInstance::Stop(bool immediate) noexcept
+{
+ pImpl->mBase.Stop(immediate, pImpl->mLooped);
+ pImpl->mPlaying = !immediate;
+}
+
+
+void SoundStreamInstance::Pause() noexcept
+{
+ pImpl->mBase.Pause();
+}
+
+
+void SoundStreamInstance::Resume()
+{
+ pImpl->mBase.Resume();
+}
+
+
+void SoundStreamInstance::SetVolume(float volume)
+{
+ pImpl->mBase.SetVolume(volume);
+}
+
+
+void SoundStreamInstance::SetPitch(float pitch)
+{
+ pImpl->mBase.SetPitch(pitch);
+}
+
+
+void SoundStreamInstance::SetPan(float pan)
+{
+ pImpl->mBase.SetPan(pan);
+}
+
+
+void SoundStreamInstance::Apply3D(const X3DAUDIO_LISTENER& listener, const X3DAUDIO_EMITTER& emitter, bool rhcoords)
+{
+ pImpl->mBase.Apply3D(listener, emitter, rhcoords);
+}
+
+
+// Public accessors.
+bool SoundStreamInstance::IsLooped() const noexcept
+{
+ return pImpl->mLooped;
+}
+
+
+SoundState SoundStreamInstance::GetState() noexcept
+{
+ const SoundState state = pImpl->mBase.GetState(pImpl->mEndStream);
+ if (state == STOPPED)
+ {
+ pImpl->mPlaying = false;
+ }
+ return state;
+}
+
+
+unsigned int SoundStreamInstance::GetChannelCount() const noexcept
+{
+ return pImpl->mBase.GetChannelCount();
+}
+
+
+IVoiceNotify* SoundStreamInstance::GetVoiceNotify() const noexcept
+{
+ return pImpl.get();
+}
diff --git a/Common/DirectXTK12/Audio/WAVFileReader.cpp b/Common/DirectXTK12/Audio/WAVFileReader.cpp
new file mode 100644
index 0000000..e05c7b4
--- /dev/null
+++ b/Common/DirectXTK12/Audio/WAVFileReader.cpp
@@ -0,0 +1,749 @@
+//--------------------------------------------------------------------------------------
+// File: WAVFileReader.cpp
+//
+// Functions for loading WAV audio files
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+// http://go.microsoft.com/fwlink/?LinkId=248929
+// http://go.microsoft.com/fwlink/?LinkID=615561
+//-------------------------------------------------------------------------------------
+
+#include "pch.h"
+#include "PlatformHelpers.h"
+#include "WAVFileReader.h"
+
+using namespace DirectX;
+
+
+namespace
+{
+ //---------------------------------------------------------------------------------
+ // .WAV files
+ //---------------------------------------------------------------------------------
+ constexpr uint32_t FOURCC_RIFF_TAG = MAKEFOURCC('R', 'I', 'F', 'F');
+ constexpr uint32_t FOURCC_FORMAT_TAG = MAKEFOURCC('f', 'm', 't', ' ');
+ constexpr uint32_t FOURCC_DATA_TAG = MAKEFOURCC('d', 'a', 't', 'a');
+ constexpr uint32_t FOURCC_WAVE_FILE_TAG = MAKEFOURCC('W', 'A', 'V', 'E');
+ constexpr uint32_t FOURCC_XWMA_FILE_TAG = MAKEFOURCC('X', 'W', 'M', 'A');
+ constexpr uint32_t FOURCC_DLS_SAMPLE = MAKEFOURCC('w', 's', 'm', 'p');
+ constexpr uint32_t FOURCC_MIDI_SAMPLE = MAKEFOURCC('s', 'm', 'p', 'l');
+ constexpr uint32_t FOURCC_XWMA_DPDS = MAKEFOURCC('d', 'p', 'd', 's');
+ constexpr uint32_t FOURCC_XMA_SEEK = MAKEFOURCC('s', 'e', 'e', 'k');
+
+ constexpr size_t SIZEOF_XMA2WAVEFORMATEX = 52;
+
+ constexpr uint16_t MSADPCM_FORMAT_EXTRA_BYTES = 32;
+
+#pragma pack(push,1)
+ struct RIFFChunk
+ {
+ uint32_t tag;
+ uint32_t size;
+ };
+
+ struct RIFFChunkHeader
+ {
+ uint32_t tag;
+ uint32_t size;
+ uint32_t riff;
+ };
+
+ struct DLSLoop
+ {
+ static constexpr uint32_t LOOP_TYPE_FORWARD = 0x00000000;
+ static constexpr uint32_t LOOP_TYPE_RELEASE = 0x00000001;
+
+ uint32_t size;
+ uint32_t loopType;
+ uint32_t loopStart;
+ uint32_t loopLength;
+ };
+
+ struct RIFFDLSSample
+ {
+ static constexpr uint32_t OPTIONS_NOTRUNCATION = 0x00000001;
+ static constexpr uint32_t OPTIONS_NOCOMPRESSION = 0x00000002;
+
+ uint32_t size;
+ uint16_t unityNote;
+ int16_t fineTune;
+ int32_t gain;
+ uint32_t options;
+ uint32_t loopCount;
+ };
+
+ struct MIDILoop
+ {
+ static constexpr uint32_t LOOP_TYPE_FORWARD = 0x00000000;
+ static constexpr uint32_t LOOP_TYPE_ALTERNATING = 0x00000001;
+ static constexpr uint32_t LOOP_TYPE_BACKWARD = 0x00000002;
+
+ uint32_t cuePointId;
+ uint32_t type;
+ uint32_t start;
+ uint32_t end;
+ uint32_t fraction;
+ uint32_t playCount;
+ };
+
+ struct RIFFMIDISample
+ {
+ uint32_t manufacturerId;
+ uint32_t productId;
+ uint32_t samplePeriod;
+ uint32_t unityNode;
+ uint32_t pitchFraction;
+ uint32_t SMPTEFormat;
+ uint32_t SMPTEOffset;
+ uint32_t loopCount;
+ uint32_t samplerData;
+ };
+#pragma pack(pop)
+
+ static_assert(sizeof(RIFFChunk) == 8, "structure size mismatch");
+ static_assert(sizeof(RIFFChunkHeader) == 12, "structure size mismatch");
+ static_assert(sizeof(DLSLoop) == 16, "structure size mismatch");
+ static_assert(sizeof(RIFFDLSSample) == 20, "structure size mismatch");
+ static_assert(sizeof(MIDILoop) == 24, "structure size mismatch");
+ static_assert(sizeof(RIFFMIDISample) == 36, "structure size mismatch");
+
+ //---------------------------------------------------------------------------------
+ const RIFFChunk* FindChunk(
+ _In_reads_bytes_(sizeBytes) const uint8_t* data,
+ _In_ size_t sizeBytes,
+ _In_ const uint8_t* upperBound,
+ _In_ uint32_t tag) noexcept
+ {
+ if (!data)
+ return nullptr;
+
+ if (sizeBytes < sizeof(RIFFChunk))
+ return nullptr;
+
+ const uint8_t* ptr = data;
+ const uint8_t* end = data + sizeBytes;
+
+ uint64_t current = 0;
+
+ while (end > (ptr + sizeof(RIFFChunk)))
+ {
+ if ((current + sizeof(RIFFChunk)) >= sizeBytes)
+ return nullptr;
+
+ auto header = reinterpret_cast(ptr);
+ if (header->tag == tag)
+ return header;
+
+ const uint64_t offset = static_cast(header->size) + sizeof(RIFFChunk);
+ current += offset;
+ if (current >= sizeBytes)
+ return nullptr;
+
+ ptr += static_cast(offset);
+
+ if (ptr >= upperBound)
+ return nullptr;
+ }
+
+ return nullptr;
+ }
+
+
+ //---------------------------------------------------------------------------------
+ HRESULT WaveFindFormatAndData(
+ _In_reads_bytes_(wavDataSize) const uint8_t* wavData,
+ _In_ size_t wavDataSize,
+ _Outptr_ const WAVEFORMATEX** pwfx,
+ _Outptr_ const uint8_t** pdata,
+ _Out_ uint32_t* dataSize,
+ _Out_ bool& dpds,
+ _Out_ bool& seek) noexcept
+ {
+ if (!wavData || !pwfx)
+ return E_POINTER;
+
+ dpds = seek = false;
+
+ if (wavDataSize < (sizeof(RIFFChunk) * 2 + sizeof(uint32_t) + sizeof(WAVEFORMAT)))
+ {
+ return E_FAIL;
+ }
+
+ const uint8_t* wavEnd = wavData + wavDataSize;
+
+ // Locate RIFF 'WAVE'
+ auto riffChunk = FindChunk(wavData, wavDataSize, wavEnd, FOURCC_RIFF_TAG);
+ if (!riffChunk || riffChunk->size < 4)
+ {
+ return E_FAIL;
+ }
+
+ if ((reinterpret_cast(riffChunk) + sizeof(RIFFChunkHeader)) > wavEnd)
+ {
+ return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
+ }
+
+ auto riffHeader = reinterpret_cast(riffChunk);
+ if (riffHeader->riff != FOURCC_WAVE_FILE_TAG && riffHeader->riff != FOURCC_XWMA_FILE_TAG)
+ {
+ return E_FAIL;
+ }
+
+ // Locate 'fmt '
+ auto ptr = reinterpret_cast(riffHeader) + sizeof(RIFFChunkHeader);
+
+ auto fmtChunk = FindChunk(ptr, riffHeader->size, wavEnd, FOURCC_FORMAT_TAG);
+ if (!fmtChunk || fmtChunk->size < sizeof(PCMWAVEFORMAT))
+ {
+ return E_FAIL;
+ }
+
+ if ((reinterpret_cast(fmtChunk) + sizeof(RIFFChunk) + sizeof(PCMWAVEFORMAT)) > wavEnd)
+ {
+ return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
+ }
+
+ ptr = reinterpret_cast(fmtChunk) + sizeof(RIFFChunk);
+ if (ptr + fmtChunk->size > wavEnd)
+ {
+ return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
+ }
+
+ auto wf = reinterpret_cast(ptr);
+
+ // Validate WAVEFORMAT (focused on chunk size and format tag, not other data that XAUDIO2 will validate)
+ switch (wf->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ case WAVE_FORMAT_IEEE_FLOAT:
+ // Can be a PCMWAVEFORMAT (16 bytes) or WAVEFORMATEX (18 bytes)
+ // We validiated chunk as at least sizeof(PCMWAVEFORMAT) above
+ break;
+
+ default:
+ {
+ if (fmtChunk->size < sizeof(WAVEFORMATEX))
+ {
+ return E_FAIL;
+ }
+
+ if ((ptr + sizeof(WAVEFORMATEX)) > wavEnd)
+ {
+ return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
+ }
+
+ auto wfx = reinterpret_cast(ptr);
+
+ if (fmtChunk->size < (sizeof(WAVEFORMATEX) + wfx->cbSize))
+ {
+ return E_FAIL;
+ }
+
+ if ((ptr + (sizeof(WAVEFORMATEX) + wfx->cbSize)) > wavEnd)
+ {
+ return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
+ }
+
+ switch (wfx->wFormatTag)
+ {
+ case WAVE_FORMAT_WMAUDIO2:
+ case WAVE_FORMAT_WMAUDIO3:
+ dpds = true;
+ break;
+
+ case 0x166 /*WAVE_FORMAT_XMA2*/: // XMA2 is supported by Xbox One & Xbox Series X|S
+ if ((fmtChunk->size < SIZEOF_XMA2WAVEFORMATEX) || (wfx->cbSize < (SIZEOF_XMA2WAVEFORMATEX - sizeof(WAVEFORMATEX))))
+ {
+ return E_FAIL;
+ }
+
+ if ((ptr + SIZEOF_XMA2WAVEFORMATEX) > wavEnd)
+ {
+ return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
+ }
+
+ seek = true;
+ break;
+
+ case WAVE_FORMAT_ADPCM:
+ if ((fmtChunk->size < (sizeof(WAVEFORMATEX) + MSADPCM_FORMAT_EXTRA_BYTES)) || (wfx->cbSize < MSADPCM_FORMAT_EXTRA_BYTES))
+ {
+ return E_FAIL;
+ }
+
+ if ((ptr + sizeof(WAVEFORMATEX) + MSADPCM_FORMAT_EXTRA_BYTES) > wavEnd)
+ {
+ return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
+ }
+ break;
+
+ case WAVE_FORMAT_EXTENSIBLE:
+ if ((fmtChunk->size < sizeof(WAVEFORMATEXTENSIBLE)) || (wfx->cbSize < (sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX))))
+ {
+ return E_FAIL;
+ }
+ else
+ {
+ static const GUID s_wfexBase = { 0x00000000, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 } };
+
+ if ((ptr + sizeof(WAVEFORMATEXTENSIBLE)) > wavEnd)
+ {
+ return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
+ }
+
+ auto wfex = reinterpret_cast(ptr);
+
+ if (memcmp(reinterpret_cast(&wfex->SubFormat) + sizeof(DWORD),
+ reinterpret_cast(&s_wfexBase) + sizeof(DWORD), sizeof(GUID) - sizeof(DWORD)) != 0)
+ {
+ return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
+ }
+
+ switch (wfex->SubFormat.Data1)
+ {
+ case WAVE_FORMAT_PCM:
+ case WAVE_FORMAT_IEEE_FLOAT:
+ break;
+
+ // MS-ADPCM and XMA2 are not supported as WAVEFORMATEXTENSIBLE
+
+ case WAVE_FORMAT_WMAUDIO2:
+ case WAVE_FORMAT_WMAUDIO3:
+ dpds = true;
+ break;
+
+ default:
+ return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
+ }
+
+ }
+ break;
+
+ default:
+ return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
+ }
+ }
+ }
+
+ // Locate 'data'
+ ptr = reinterpret_cast(riffHeader) + sizeof(RIFFChunkHeader);
+ if ((ptr + sizeof(RIFFChunk)) > wavEnd)
+ {
+ return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
+ }
+
+ auto dataChunk = FindChunk(ptr, riffChunk->size, wavEnd, FOURCC_DATA_TAG);
+ if (!dataChunk || !dataChunk->size)
+ {
+ return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
+ }
+
+ ptr = reinterpret_cast(dataChunk) + sizeof(RIFFChunk);
+ if (ptr + dataChunk->size > wavEnd)
+ {
+ return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
+ }
+
+ *pwfx = reinterpret_cast(wf);
+ *pdata = ptr;
+ *dataSize = dataChunk->size;
+ return S_OK;
+ }
+
+
+ //---------------------------------------------------------------------------------
+ HRESULT WaveFindLoopInfo(
+ _In_reads_bytes_(wavDataSize) const uint8_t* wavData,
+ _In_ size_t wavDataSize,
+ _Out_ uint32_t* pLoopStart,
+ _Out_ uint32_t* pLoopLength) noexcept
+ {
+ if (!wavData || !pLoopStart || !pLoopLength)
+ return E_POINTER;
+
+ if (wavDataSize < (sizeof(RIFFChunk) + sizeof(uint32_t)))
+ {
+ return E_FAIL;
+ }
+
+ *pLoopStart = 0;
+ *pLoopLength = 0;
+
+ const uint8_t* wavEnd = wavData + wavDataSize;
+
+ // Locate RIFF 'WAVE'
+ auto riffChunk = FindChunk(wavData, wavDataSize, wavEnd, FOURCC_RIFF_TAG);
+ if (!riffChunk || riffChunk->size < 4)
+ {
+ return E_FAIL;
+ }
+
+ auto riffHeader = reinterpret_cast(riffChunk);
+ if (riffHeader->riff == FOURCC_XWMA_FILE_TAG)
+ {
+ // xWMA files do not contain loop information
+ return S_OK;
+ }
+
+ if (riffHeader->riff != FOURCC_WAVE_FILE_TAG)
+ {
+ return E_FAIL;
+ }
+
+ // Locate 'wsmp' (DLS Chunk)
+ auto ptr = reinterpret_cast(riffHeader) + sizeof(RIFFChunkHeader);
+ if ((ptr + sizeof(RIFFChunk)) > wavEnd)
+ {
+ return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
+ }
+
+ auto dlsChunk = FindChunk(ptr, riffChunk->size, wavEnd, FOURCC_DLS_SAMPLE);
+ if (dlsChunk)
+ {
+ ptr = reinterpret_cast(dlsChunk) + sizeof(RIFFChunk);
+ if (ptr + dlsChunk->size > wavEnd)
+ {
+ return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
+ }
+
+ if (dlsChunk->size >= sizeof(RIFFDLSSample))
+ {
+ auto dlsSample = reinterpret_cast(ptr);
+
+ if (dlsChunk->size >= (dlsSample->size + dlsSample->loopCount * sizeof(DLSLoop)))
+ {
+ auto loops = reinterpret_cast(ptr + dlsSample->size);
+ for (uint32_t j = 0; j < dlsSample->loopCount; ++j)
+ {
+ if ((loops[j].loopType == DLSLoop::LOOP_TYPE_FORWARD || loops[j].loopType == DLSLoop::LOOP_TYPE_RELEASE))
+ {
+ // Return 'forward' loop
+ *pLoopStart = loops[j].loopStart;
+ *pLoopLength = loops[j].loopLength;
+ return S_OK;
+ }
+ }
+ }
+ }
+ }
+
+ // Locate 'smpl' (Sample Chunk)
+ auto midiChunk = FindChunk(ptr, riffChunk->size, wavEnd, FOURCC_MIDI_SAMPLE);
+ if (midiChunk)
+ {
+ ptr = reinterpret_cast(midiChunk) + sizeof(RIFFChunk);
+ if (ptr + midiChunk->size > wavEnd)
+ {
+ return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
+ }
+
+ if (midiChunk->size >= sizeof(RIFFMIDISample))
+ {
+ auto midiSample = reinterpret_cast(ptr);
+
+ if (midiChunk->size >= (sizeof(RIFFMIDISample) + midiSample->loopCount * sizeof(MIDILoop)))
+ {
+ auto loops = reinterpret_cast(ptr + sizeof(RIFFMIDISample));
+ for (uint32_t j = 0; j < midiSample->loopCount; ++j)
+ {
+ if (loops[j].type == MIDILoop::LOOP_TYPE_FORWARD)
+ {
+ // Return 'forward' loop
+ *pLoopStart = loops[j].start;
+ *pLoopLength = loops[j].end - loops[j].start + 1;
+ return S_OK;
+ }
+ }
+ }
+ }
+ }
+
+ return S_OK;
+ }
+
+
+ //---------------------------------------------------------------------------------
+ HRESULT WaveFindTable(
+ _In_reads_bytes_(wavDataSize) const uint8_t* wavData,
+ _In_ size_t wavDataSize,
+ _In_ uint32_t tag,
+ _Outptr_result_maybenull_ const uint32_t** pData,
+ _Out_ uint32_t* dataCount) noexcept
+ {
+ if (!wavData || !pData || !dataCount)
+ return E_POINTER;
+
+ if (wavDataSize < (sizeof(RIFFChunk) + sizeof(uint32_t)))
+ {
+ return E_FAIL;
+ }
+
+ *pData = nullptr;
+ *dataCount = 0;
+
+ const uint8_t* wavEnd = wavData + wavDataSize;
+
+ // Locate RIFF 'WAVE'
+ auto riffChunk = FindChunk(wavData, wavDataSize, wavEnd, FOURCC_RIFF_TAG);
+ if (!riffChunk || riffChunk->size < 4)
+ {
+ return E_FAIL;
+ }
+
+ auto riffHeader = reinterpret_cast(riffChunk);
+ if (riffHeader->riff != FOURCC_WAVE_FILE_TAG && riffHeader->riff != FOURCC_XWMA_FILE_TAG)
+ {
+ return E_FAIL;
+ }
+
+ // Locate tag
+ auto ptr = reinterpret_cast(riffHeader) + sizeof(RIFFChunkHeader);
+ if ((ptr + sizeof(RIFFChunk)) > wavEnd)
+ {
+ return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
+ }
+
+ auto tableChunk = FindChunk(ptr, riffChunk->size, wavEnd, tag);
+ if (tableChunk)
+ {
+ ptr = reinterpret_cast(tableChunk) + sizeof(RIFFChunk);
+ if (ptr + tableChunk->size > wavEnd)
+ {
+ return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
+ }
+
+ if ((tableChunk->size % sizeof(uint32_t)) != 0)
+ {
+ return E_FAIL;
+ }
+
+ *pData = reinterpret_cast(ptr);
+ *dataCount = tableChunk->size / 4;
+ }
+
+ return S_OK;
+ }
+
+
+ //---------------------------------------------------------------------------------
+ HRESULT LoadAudioFromFile(
+ _In_z_ const wchar_t* szFileName,
+ _Inout_ std::unique_ptr& wavData,
+ _Out_ DWORD* bytesRead) noexcept
+ {
+ if (!szFileName)
+ return E_INVALIDARG;
+
+ // open the file
+ #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
+ ScopedHandle hFile(safe_handle(CreateFile2(
+ szFileName,
+ GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING,
+ nullptr)));
+ #else
+ ScopedHandle hFile(safe_handle(CreateFileW(
+ szFileName,
+ GENERIC_READ, FILE_SHARE_READ,
+ nullptr,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
+ nullptr)));
+ #endif
+
+ if (!hFile)
+ {
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+
+ // Get the file size
+ FILE_STANDARD_INFO fileInfo;
+ if (!GetFileInformationByHandleEx(hFile.get(), FileStandardInfo, &fileInfo, sizeof(fileInfo)))
+ {
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+
+ // File is too big for 32-bit allocation, so reject read
+ if (fileInfo.EndOfFile.HighPart > 0)
+ {
+ return E_FAIL;
+ }
+
+ // Need at least enough data to have a valid minimal WAV file
+ if (fileInfo.EndOfFile.LowPart < (sizeof(RIFFChunk) * 2 + sizeof(DWORD) + sizeof(WAVEFORMAT)))
+ {
+ return E_FAIL;
+ }
+
+ // create enough space for the file data
+ wavData.reset(new (std::nothrow) uint8_t[fileInfo.EndOfFile.LowPart]);
+ if (!wavData)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ // read the data in
+ if (!ReadFile(hFile.get(),
+ wavData.get(),
+ fileInfo.EndOfFile.LowPart,
+ bytesRead,
+ nullptr
+ ))
+ {
+ return HRESULT_FROM_WIN32(GetLastError());
+ }
+
+ return (*bytesRead < fileInfo.EndOfFile.LowPart) ? E_FAIL : S_OK;
+ }
+}
+
+//-------------------------------------------------------------------------------------
+_Use_decl_annotations_
+HRESULT DirectX::LoadWAVAudioInMemory(
+ const uint8_t* wavData,
+ size_t wavDataSize,
+ const WAVEFORMATEX** wfx,
+ const uint8_t** startAudio,
+ uint32_t* audioBytes) noexcept
+{
+ if (!wavData || !wfx || !startAudio || !audioBytes)
+ return E_INVALIDARG;
+
+ *wfx = nullptr;
+ *startAudio = nullptr;
+ *audioBytes = 0;
+
+ // Need at least enough data to have a valid minimal WAV file
+ if (wavDataSize < (sizeof(RIFFChunk) * 2 + sizeof(DWORD) + sizeof(WAVEFORMAT)))
+ {
+ return E_FAIL;
+ }
+
+ bool dpds, seek;
+ HRESULT hr = WaveFindFormatAndData(wavData, wavDataSize, wfx, startAudio, audioBytes, dpds, seek);
+ if (FAILED(hr))
+ return hr;
+
+ return (dpds || seek) ? E_FAIL : S_OK;
+}
+
+
+//-------------------------------------------------------------------------------------
+_Use_decl_annotations_
+HRESULT DirectX::LoadWAVAudioFromFile(
+ const wchar_t* szFileName,
+ std::unique_ptr& wavData,
+ const WAVEFORMATEX** wfx,
+ const uint8_t** startAudio,
+ uint32_t* audioBytes) noexcept
+{
+ if (!szFileName || !wfx || !startAudio || !audioBytes)
+ return E_INVALIDARG;
+
+ *wfx = nullptr;
+ *startAudio = nullptr;
+ *audioBytes = 0;
+
+ DWORD bytesRead = 0;
+ HRESULT hr = LoadAudioFromFile(szFileName, wavData, &bytesRead);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ bool dpds, seek;
+ hr = WaveFindFormatAndData(wavData.get(), bytesRead, wfx, startAudio, audioBytes, dpds, seek);
+ if (FAILED(hr))
+ return hr;
+
+ return (dpds || seek) ? E_FAIL : S_OK;
+}
+
+
+//-------------------------------------------------------------------------------------
+_Use_decl_annotations_
+HRESULT DirectX::LoadWAVAudioInMemoryEx(
+ const uint8_t* wavData,
+ size_t wavDataSize,
+ DirectX::WAVData& result) noexcept
+{
+ if (!wavData)
+ return E_INVALIDARG;
+
+ memset(&result, 0, sizeof(result));
+
+ // Need at least enough data to have a valid minimal WAV file
+ if (wavDataSize < (sizeof(RIFFChunk) * 2 + sizeof(DWORD) + sizeof(WAVEFORMAT)))
+ {
+ return E_FAIL;
+ }
+
+ bool dpds, seek;
+ HRESULT hr = WaveFindFormatAndData(wavData, wavDataSize, &result.wfx, &result.startAudio, &result.audioBytes, dpds, seek);
+ if (FAILED(hr))
+ return hr;
+
+ hr = WaveFindLoopInfo(wavData, wavDataSize, &result.loopStart, &result.loopLength);
+ if (FAILED(hr))
+ return hr;
+
+ if (dpds)
+ {
+ hr = WaveFindTable(wavData, wavDataSize, FOURCC_XWMA_DPDS, &result.seek, &result.seekCount);
+ if (FAILED(hr))
+ return hr;
+ }
+ else if (seek)
+ {
+ hr = WaveFindTable(wavData, wavDataSize, FOURCC_XMA_SEEK, &result.seek, &result.seekCount);
+ if (FAILED(hr))
+ return hr;
+ }
+
+ return S_OK;
+}
+
+
+//-------------------------------------------------------------------------------------
+_Use_decl_annotations_
+HRESULT DirectX::LoadWAVAudioFromFileEx(
+ const wchar_t* szFileName,
+ std::unique_ptr& wavData,
+ DirectX::WAVData& result) noexcept
+{
+ if (!szFileName)
+ return E_INVALIDARG;
+
+ memset(&result, 0, sizeof(result));
+
+ DWORD bytesRead = 0;
+ HRESULT hr = LoadAudioFromFile(szFileName, wavData, &bytesRead);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ bool dpds, seek;
+ hr = WaveFindFormatAndData(wavData.get(), bytesRead, &result.wfx, &result.startAudio, &result.audioBytes, dpds, seek);
+ if (FAILED(hr))
+ return hr;
+
+ hr = WaveFindLoopInfo(wavData.get(), bytesRead, &result.loopStart, &result.loopLength);
+ if (FAILED(hr))
+ return hr;
+
+ if (dpds)
+ {
+ hr = WaveFindTable(wavData.get(), bytesRead, FOURCC_XWMA_DPDS, &result.seek, &result.seekCount);
+ if (FAILED(hr))
+ return hr;
+ }
+ else if (seek)
+ {
+ hr = WaveFindTable(wavData.get(), bytesRead, FOURCC_XMA_SEEK, &result.seek, &result.seekCount);
+ if (FAILED(hr))
+ return hr;
+ }
+
+ return S_OK;
+}
diff --git a/Common/DirectXTK12/Audio/WAVFileReader.h b/Common/DirectXTK12/Audio/WAVFileReader.h
new file mode 100644
index 0000000..c919179
--- /dev/null
+++ b/Common/DirectXTK12/Audio/WAVFileReader.h
@@ -0,0 +1,58 @@
+//--------------------------------------------------------------------------------------
+// File: WAVFileReader.h
+//
+// Functions for loading WAV audio files
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+// http://go.microsoft.com/fwlink/?LinkId=248929
+// http://go.microsoft.com/fwlink/?LinkID=615561
+//-------------------------------------------------------------------------------------
+
+#pragma once
+
+#include
+
+#include
+#include
+#include
+
+
+namespace DirectX
+{
+ HRESULT LoadWAVAudioInMemory(
+ _In_reads_bytes_(wavDataSize) const uint8_t* wavData,
+ _In_ size_t wavDataSize,
+ _Outptr_ const WAVEFORMATEX** wfx,
+ _Outptr_ const uint8_t** startAudio,
+ _Out_ uint32_t* audioBytes) noexcept;
+
+ HRESULT LoadWAVAudioFromFile(
+ _In_z_ const wchar_t* szFileName,
+ _Inout_ std::unique_ptr& wavData,
+ _Outptr_ const WAVEFORMATEX** wfx,
+ _Outptr_ const uint8_t** startAudio,
+ _Out_ uint32_t* audioBytes) noexcept;
+
+ struct WAVData
+ {
+ const WAVEFORMATEX* wfx;
+ const uint8_t* startAudio;
+ uint32_t audioBytes;
+ uint32_t loopStart;
+ uint32_t loopLength;
+ const uint32_t* seek; // Note: XMA Seek data is Big-Endian
+ uint32_t seekCount;
+ };
+
+ HRESULT LoadWAVAudioInMemoryEx(
+ _In_reads_bytes_(wavDataSize) const uint8_t* wavData,
+ _In_ size_t wavDataSize,
+ _Out_ WAVData& result) noexcept;
+
+ HRESULT LoadWAVAudioFromFileEx(
+ _In_z_ const wchar_t* szFileName,
+ _Inout_ std::unique_ptr& wavData,
+ _Out_ WAVData& result) noexcept;
+}
diff --git a/Common/DirectXTK12/Audio/WaveBank.cpp b/Common/DirectXTK12/Audio/WaveBank.cpp
new file mode 100644
index 0000000..f169b18
--- /dev/null
+++ b/Common/DirectXTK12/Audio/WaveBank.cpp
@@ -0,0 +1,618 @@
+//--------------------------------------------------------------------------------------
+// File: WaveBank.cpp
+//
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+// http://go.microsoft.com/fwlink/?LinkId=248929
+// http://go.microsoft.com/fwlink/?LinkID=615561
+//--------------------------------------------------------------------------------------
+
+#include "pch.h"
+#include "Audio.h"
+#include "WaveBankReader.h"
+#include "SoundCommon.h"
+#include "PlatformHelpers.h"
+
+#include
+
+using namespace DirectX;
+
+
+//======================================================================================
+// WaveBank
+//======================================================================================
+
+// Internal object implementation class.
+class WaveBank::Impl : public IVoiceNotify
+{
+public:
+ explicit Impl(_In_ AudioEngine* engine) :
+ mEngine(engine),
+ mOneShots(0),
+ mPrepared(false),
+ mStreaming(false)
+ {
+ assert(mEngine != nullptr);
+ mEngine->RegisterNotify(this, false);
+ }
+
+ Impl(Impl&&) = default;
+ Impl& operator= (Impl&&) = default;
+
+ Impl(Impl const&) = delete;
+ Impl& operator= (Impl const&) = delete;
+
+ ~Impl() override
+ {
+ if (!mInstances.empty())
+ {
+ DebugTrace("WARNING: Destroying WaveBank \"%hs\" with %zu outstanding instances\n",
+ mReader.BankName(), mInstances.size());
+
+ for (auto it : mInstances)
+ {
+ assert(it != nullptr);
+ it->OnDestroyParent();
+ }
+
+ mInstances.clear();
+ }
+
+ if (mOneShots > 0)
+ {
+ DebugTrace("WARNING: Destroying WaveBank \"%hs\" with %u outstanding one shot effects\n",
+ mReader.BankName(), mOneShots);
+ }
+
+ if (mEngine)
+ {
+ mEngine->UnregisterNotify(this, true, false);
+ mEngine = nullptr;
+ }
+ }
+
+ HRESULT Initialize(_In_ const AudioEngine* engine, _In_z_ const wchar_t* wbFileName) noexcept;
+
+ void Play(unsigned int index, float volume, float pitch, float pan);
+
+ // IVoiceNotify
+ void __cdecl OnBufferEnd() override
+ {
+ InterlockedDecrement(&mOneShots);
+ }
+
+ void __cdecl OnCriticalError() override
+ {
+ mOneShots = 0;
+ }
+
+ void __cdecl OnReset() override
+ {
+ // No action required
+ }
+
+ void __cdecl OnUpdate() override
+ {
+ // We do not register for update notification
+ assert(false);
+ }
+
+ void __cdecl OnDestroyEngine() noexcept override
+ {
+ mEngine = nullptr;
+ mOneShots = 0;
+ }
+
+ void __cdecl OnTrim() override
+ {
+ // No action required
+ }
+
+ void __cdecl GatherStatistics(AudioStatistics& stats) const noexcept override
+ {
+ stats.playingOneShots += mOneShots;
+
+ if (!mStreaming)
+ {
+ stats.audioBytes += mReader.BankAudioSize();
+
+ #ifdef DIRECTX_ENABLE_XMA2
+ if (mReader.HasXMA())
+ stats.xmaAudioBytes += mReader.BankAudioSize();
+ #endif
+ }
+ }
+
+ void __cdecl OnDestroyParent() noexcept override
+ {
+ }
+
+ AudioEngine* mEngine;
+ std::list mInstances;
+ WaveBankReader mReader;
+ uint32_t mOneShots;
+ bool mPrepared;
+ bool mStreaming;
+};
+
+
+_Use_decl_annotations_
+HRESULT WaveBank::Impl::Initialize(const AudioEngine* engine, const wchar_t* wbFileName) noexcept
+{
+ if (!engine || !wbFileName)
+ return E_INVALIDARG;
+
+ HRESULT hr = mReader.Open(wbFileName);
+ if (FAILED(hr))
+ return hr;
+
+ mStreaming = mReader.IsStreamingBank();
+
+ return S_OK;
+}
+
+
+void WaveBank::Impl::Play(unsigned int index, float volume, float pitch, float pan)
+{
+ assert(volume >= -XAUDIO2_MAX_VOLUME_LEVEL && volume <= XAUDIO2_MAX_VOLUME_LEVEL);
+ assert(pitch >= -1.f && pitch <= 1.f);
+ assert(pan >= -1.f && pan <= 1.f);
+
+ if (mStreaming)
+ {
+ DebugTrace("ERROR: One-shots can only be created from an in-memory wave bank\n");
+ throw std::runtime_error("WaveBank::Play");
+ }
+
+ if (index >= mReader.Count())
+ {
+ DebugTrace("WARNING: Index %u not found in wave bank with only %u entries, one-shot not triggered\n",
+ index, mReader.Count());
+ return;
+ }
+
+ if (!mPrepared)
+ {
+ mReader.WaitOnPrepare();
+ mPrepared = true;
+ }
+
+ char wfxbuff[64] = {};
+ auto wfx = reinterpret_cast(wfxbuff);
+ HRESULT hr = mReader.GetFormat(index, wfx, sizeof(wfxbuff));
+ ThrowIfFailed(hr);
+
+ IXAudio2SourceVoice* voice = nullptr;
+ mEngine->AllocateVoice(wfx, SoundEffectInstance_Default, true, &voice);
+
+ if (!voice)
+ return;
+
+ if (volume != 1.f)
+ {
+ hr = voice->SetVolume(volume);
+ ThrowIfFailed(hr);
+ }
+
+ if (pitch != 0.f)
+ {
+ const float fr = XAudio2SemitonesToFrequencyRatio(pitch * 12.f);
+
+ hr = voice->SetFrequencyRatio(fr);
+ ThrowIfFailed(hr);
+ }
+
+ if (pan != 0.f)
+ {
+ float matrix[16];
+ if (ComputePan(pan, wfx->nChannels, matrix))
+ {
+ hr = voice->SetOutputMatrix(nullptr, wfx->nChannels, mEngine->GetOutputChannels(), matrix);
+ ThrowIfFailed(hr);
+ }
+ }
+
+ hr = voice->Start(0);
+ ThrowIfFailed(hr);
+
+ XAUDIO2_BUFFER buffer = {};
+ hr = mReader.GetWaveData(index, &buffer.pAudioData, buffer.AudioBytes);
+ ThrowIfFailed(hr);
+
+ WaveBankReader::Metadata metadata;
+ hr = mReader.GetMetadata(index, metadata);
+ ThrowIfFailed(hr);
+
+ buffer.Flags = XAUDIO2_END_OF_STREAM;
+ buffer.pContext = this;
+
+#ifdef DIRECTX_ENABLE_XWMA
+
+ XAUDIO2_BUFFER_WMA wmaBuffer = {};
+
+ uint32_t tag;
+ hr = mReader.GetSeekTable(index, &wmaBuffer.pDecodedPacketCumulativeBytes, wmaBuffer.PacketCount, tag);
+ ThrowIfFailed(hr);
+
+ if (tag == WAVE_FORMAT_WMAUDIO2 || tag == WAVE_FORMAT_WMAUDIO3)
+ {
+ hr = voice->SubmitSourceBuffer(&buffer, &wmaBuffer);
+ }
+ else
+ #endif // xWMA
+ {
+ hr = voice->SubmitSourceBuffer(&buffer, nullptr);
+ }
+ if (FAILED(hr))
+ {
+ DebugTrace("ERROR: WaveBank failed (%08X) when submitting buffer:\n", static_cast(hr));
+ DebugTrace("\tFormat Tag %u, %u channels, %u-bit, %u Hz, %u bytes\n",
+ wfx->wFormatTag, wfx->nChannels, wfx->wBitsPerSample, wfx->nSamplesPerSec, metadata.lengthBytes);
+ throw std::runtime_error("SubmitSourceBuffer");
+ }
+
+ InterlockedIncrement(&mOneShots);
+}
+
+
+//--------------------------------------------------------------------------------------
+// WaveBank
+//--------------------------------------------------------------------------------------
+
+// Public constructors.
+_Use_decl_annotations_
+WaveBank::WaveBank(AudioEngine* engine, const wchar_t* wbFileName)
+ : pImpl(std::make_unique(engine))
+{
+ HRESULT hr = pImpl->Initialize(engine, wbFileName);
+ if (FAILED(hr))
+ {
+ DebugTrace("ERROR: WaveBank failed (%08X) to intialize from .xwb file \"%ls\"\n",
+ static_cast(hr), wbFileName);
+ throw std::runtime_error("WaveBank");
+ }
+
+ DebugTrace("INFO: WaveBank \"%hs\" with %u entries loaded from .xwb file \"%ls\"\n",
+ pImpl->mReader.BankName(), pImpl->mReader.Count(), wbFileName);
+}
+
+
+WaveBank::WaveBank(WaveBank&&) noexcept = default;
+WaveBank& WaveBank::operator= (WaveBank&&) noexcept = default;
+WaveBank::~WaveBank() = default;
+
+
+// Public methods (one-shots)
+void WaveBank::Play(unsigned int index)
+{
+ pImpl->Play(index, 1.f, 0.f, 0.f);
+}
+
+
+void WaveBank::Play(unsigned int index, float volume, float pitch, float pan)
+{
+ pImpl->Play(index, volume, pitch, pan);
+}
+
+
+void WaveBank::Play(_In_z_ const char* name)
+{
+ const unsigned int index = pImpl->mReader.Find(name);
+ if (index == unsigned(-1))
+ {
+ DebugTrace("WARNING: Name '%hs' not found in wave bank, one-shot not triggered\n", name);
+ return;
+ }
+
+ pImpl->Play(index, 1.f, 0.f, 0.f);
+}
+
+
+void WaveBank::Play(_In_z_ const char* name, float volume, float pitch, float pan)
+{
+ const unsigned int index = pImpl->mReader.Find(name);
+ if (index == unsigned(-1))
+ {
+ DebugTrace("WARNING: Name '%hs' not found in wave bank, one-shot not triggered\n", name);
+ return;
+ }
+
+ pImpl->Play(index, volume, pitch, pan);
+}
+
+
+// Public methods (sound effect instance)
+std::unique_ptr WaveBank::CreateInstance(unsigned int index, SOUND_EFFECT_INSTANCE_FLAGS flags)
+{
+ auto& wb = pImpl->mReader;
+
+ if (pImpl->mStreaming)
+ {
+ DebugTrace("ERROR: SoundEffectInstances can only be created from an in-memory wave bank\n");
+ throw std::runtime_error("WaveBank::CreateInstance");
+ }
+
+ if (index >= wb.Count())
+ {
+ // We don't throw an exception here as titles often simply ignore missing assets rather than fail
+ return std::unique_ptr();
+ }
+
+ if (!pImpl->mPrepared)
+ {
+ wb.WaitOnPrepare();
+ pImpl->mPrepared = true;
+ }
+
+ auto effect = new SoundEffectInstance(pImpl->mEngine, this, index, flags);
+ assert(effect != nullptr);
+ pImpl->mInstances.emplace_back(effect->GetVoiceNotify());
+ return std::unique_ptr(effect);
+}
+
+
+std::unique_ptr WaveBank::CreateInstance(_In_z_ const char* name, SOUND_EFFECT_INSTANCE_FLAGS flags)
+{
+ const unsigned int index = pImpl->mReader.Find(name);
+ if (index == unsigned(-1))
+ {
+ // We don't throw an exception here as titles often simply ignore missing assets rather than fail
+ return std::unique_ptr();
+ }
+
+ return CreateInstance(index, flags);
+}
+
+
+// Public methods (sound stream instance)
+std::unique_ptr WaveBank::CreateStreamInstance(unsigned int index, SOUND_EFFECT_INSTANCE_FLAGS flags)
+{
+ auto& wb = pImpl->mReader;
+
+ if (!pImpl->mStreaming)
+ {
+ DebugTrace("ERROR: SoundStreamInstances can only be created from a streaming wave bank\n");
+ throw std::runtime_error("WaveBank::CreateStreamInstance");
+ }
+
+ if (index >= wb.Count())
+ {
+ // We don't throw an exception here as titles often simply ignore missing assets rather than fail
+ return std::unique_ptr();
+ }
+
+ if (!pImpl->mPrepared)
+ {
+ wb.WaitOnPrepare();
+ pImpl->mPrepared = true;
+ }
+
+ auto effect = new SoundStreamInstance(pImpl->mEngine, this, index, flags);
+ assert(effect != nullptr);
+ pImpl->mInstances.emplace_back(effect->GetVoiceNotify());
+ return std::unique_ptr(effect);
+}
+
+
+std::unique_ptr WaveBank::CreateStreamInstance(_In_z_ const char* name, SOUND_EFFECT_INSTANCE_FLAGS flags)
+{
+ const unsigned int index = pImpl->mReader.Find(name);
+ if (index == unsigned(-1))
+ {
+ // We don't throw an exception here as titles often simply ignore missing assets rather than fail
+ return std::unique_ptr