//-------------------------------------------------------------------------------------- // File: WaveBank.cpp // // Copyright (c) Microsoft Corporation. All rights reserved. // 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.begin(); it != mInstances.end(); ++it) { 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_ 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(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::exception("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) { 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::exception("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::exception("WaveBank"); } DebugTrace("INFO: WaveBank \"%hs\" with %u entries loaded from .xwb file \"%ls\"\n", pImpl->mReader.BankName(), pImpl->mReader.Count(), wbFileName); } // Move constructor. WaveBank::WaveBank(WaveBank&& moveFrom) noexcept : pImpl(std::move(moveFrom.pImpl)) { } // Move assignment. WaveBank& WaveBank::operator= (WaveBank&& moveFrom) noexcept { pImpl = std::move(moveFrom.pImpl); return *this; } // Public destructor. WaveBank::~WaveBank() { } // 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) { 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) { 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::exception("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) { 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::exception("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) { 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 CreateStreamInstance(index, flags); } void WaveBank::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 WaveBank::IsPrepared() const noexcept { if (pImpl->mPrepared) return true; if (!pImpl->mReader.IsPrepared()) return false; pImpl->mPrepared = true; return true; } bool WaveBank::IsInUse() const noexcept { return (pImpl->mOneShots > 0) || !pImpl->mInstances.empty(); } bool WaveBank::IsStreamingBank() const noexcept { return pImpl->mReader.IsStreamingBank(); } size_t WaveBank::GetSampleSizeInBytes(unsigned int index) const noexcept { if (index >= pImpl->mReader.Count()) return 0; WaveBankReader::Metadata metadata; HRESULT hr = pImpl->mReader.GetMetadata(index, metadata); if (FAILED(hr)) return 0; return metadata.lengthBytes; } size_t WaveBank::GetSampleDuration(unsigned int index) const noexcept { if (index >= pImpl->mReader.Count()) return 0; WaveBankReader::Metadata metadata; HRESULT hr = pImpl->mReader.GetMetadata(index, metadata); if (FAILED(hr)) return 0; return metadata.duration; } size_t WaveBank::GetSampleDurationMS(unsigned int index) const noexcept { if (index >= pImpl->mReader.Count()) return 0; char buff[64] = {}; auto wfx = reinterpret_cast(buff); HRESULT hr = pImpl->mReader.GetFormat(index, wfx, sizeof(buff)); if (FAILED(hr)) return 0; WaveBankReader::Metadata metadata; hr = pImpl->mReader.GetMetadata(index, metadata); if (FAILED(hr)) return 0; return static_cast((uint64_t(metadata.duration) * 1000) / wfx->nSamplesPerSec); } _Use_decl_annotations_ const WAVEFORMATEX* WaveBank::GetFormat(unsigned int index, WAVEFORMATEX* wfx, size_t maxsize) const noexcept { if (index >= pImpl->mReader.Count()) return nullptr; HRESULT hr = pImpl->mReader.GetFormat(index, wfx, maxsize); if (FAILED(hr)) return nullptr; return wfx; } _Use_decl_annotations_ int WaveBank::Find(const char* name) const { return static_cast(pImpl->mReader.Find(name)); } #ifdef DIRECTX_ENABLE_XWMA _Use_decl_annotations_ bool WaveBank::FillSubmitBuffer(unsigned int index, XAUDIO2_BUFFER& buffer, XAUDIO2_BUFFER_WMA& wmaBuffer) const { memset(&buffer, 0, sizeof(buffer)); memset(&wmaBuffer, 0, sizeof(wmaBuffer)); HRESULT hr = pImpl->mReader.GetWaveData(index, &buffer.pAudioData, buffer.AudioBytes); ThrowIfFailed(hr); WaveBankReader::Metadata metadata; hr = pImpl->mReader.GetMetadata(index, metadata); ThrowIfFailed(hr); buffer.LoopBegin = metadata.loopStart; buffer.LoopLength = metadata.loopLength; uint32_t tag; hr = pImpl->mReader.GetSeekTable(index, &wmaBuffer.pDecodedPacketCumulativeBytes, wmaBuffer.PacketCount, tag); ThrowIfFailed(hr); return (tag == WAVE_FORMAT_WMAUDIO2 || tag == WAVE_FORMAT_WMAUDIO3); } #else // !xWMA _Use_decl_annotations_ void WaveBank::FillSubmitBuffer(unsigned int index, XAUDIO2_BUFFER& buffer) const { memset(&buffer, 0, sizeof(buffer)); HRESULT hr = pImpl->mReader.GetWaveData(index, &buffer.pAudioData, buffer.AudioBytes); ThrowIfFailed(hr); WaveBankReader::Metadata metadata; hr = pImpl->mReader.GetMetadata(index, metadata); ThrowIfFailed(hr); buffer.LoopBegin = metadata.loopStart; buffer.LoopLength = metadata.loopLength; } #endif HANDLE WaveBank::GetAsyncHandle() const noexcept { if (pImpl) { return pImpl->mReader.GetAsyncHandle(); } return nullptr; } _Use_decl_annotations_ bool WaveBank::GetPrivateData(unsigned int index, void* data, size_t datasize) { if (index >= pImpl->mReader.Count()) return false; if (!data) return false; switch (datasize) { case sizeof(WaveBankReader::Metadata): { auto ptr = reinterpret_cast(data); return SUCCEEDED(pImpl->mReader.GetMetadata(index, *ptr)); } case sizeof(WaveBankSeekData): { auto ptr = reinterpret_cast(data); return SUCCEEDED(pImpl->mReader.GetSeekTable(index, &ptr->seekTable, ptr->seekCount, ptr->tag)); } default: return false; } }