//-------------------------------------------------------------------------------------- // File: SoundEffect.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 "WAVFileReader.h" #include "SoundCommon.h" #include #if (defined(_XBOX_ONE) && defined(_TITLE)) || defined(_GAMING_XBOX) #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.begin(); it != mInstances.end(); ++it) { 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_ 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(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) { 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 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::exception("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::exception("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::exception("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::exception("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::exception("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::exception("SoundEffect"); } } #endif // Move constructor. SoundEffect::SoundEffect(SoundEffect&& moveFrom) noexcept : pImpl(std::move(moveFrom.pImpl)) { } // Move assignment. SoundEffect& SoundEffect::operator= (SoundEffect&& moveFrom) noexcept { pImpl = std::move(moveFrom.pImpl); return *this; } // Public destructor. SoundEffect::~SoundEffect() { } // 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; 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; 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; 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