616 строки
16 KiB
C++
616 строки
16 KiB
C++
//--------------------------------------------------------------------------------------
|
|
// 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 <list>
|
|
|
|
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<IVoiceNotify*> 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<WAVEFORMATEX*>(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<unsigned int>(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<Impl>(engine))
|
|
{
|
|
HRESULT hr = pImpl->Initialize(engine, wbFileName);
|
|
if (FAILED(hr))
|
|
{
|
|
DebugTrace("ERROR: WaveBank failed (%08X) to intialize from .xwb file \"%ls\"\n",
|
|
static_cast<unsigned int>(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<SoundEffectInstance> 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<SoundEffectInstance>();
|
|
}
|
|
|
|
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<SoundEffectInstance>(effect);
|
|
}
|
|
|
|
|
|
std::unique_ptr<SoundEffectInstance> 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<SoundEffectInstance>();
|
|
}
|
|
|
|
return CreateInstance(index, flags);
|
|
}
|
|
|
|
|
|
// Public methods (sound stream instance)
|
|
std::unique_ptr<SoundStreamInstance> 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<SoundStreamInstance>();
|
|
}
|
|
|
|
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<SoundStreamInstance>(effect);
|
|
}
|
|
|
|
|
|
std::unique_ptr<SoundStreamInstance> 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<SoundStreamInstance>();
|
|
}
|
|
|
|
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<WAVEFORMATEX*>(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<size_t>((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<int>(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<WaveBankReader::Metadata*>(data);
|
|
return SUCCEEDED(pImpl->mReader.GetMetadata(index, *ptr));
|
|
}
|
|
|
|
case sizeof(WaveBankSeekData):
|
|
{
|
|
auto ptr = reinterpret_cast<WaveBankSeekData*>(data);
|
|
return SUCCEEDED(pImpl->mReader.GetSeekTable(index, &ptr->seekTable, ptr->seekCount, ptr->tag));
|
|
}
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|