1
0
mirror of https://github.com/ncblakely/GiantsTools synced 2024-12-23 15:57:22 +01:00
GiantsTools/Sdk/External/DirectXTK/Audio/SoundStreamInstance.cpp

850 lines
25 KiB
C++
Raw Normal View History

2021-01-24 00:40:09 +01:00
//--------------------------------------------------------------------------------------
// File: SoundStreamInstance.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 "DirectXHelpers.h"
#include "WaveBankReader.h"
#include "PlatformHelpers.h"
#include "SoundCommon.h"
#if (defined(_XBOX_ONE) && defined(_TITLE)) || defined(_GAMING_XBOX)
#include <apu.h>
#include <shapexmacontext.h>
#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
{
const size_t DVD_SECTOR_SIZE = 2048;
const size_t MEMORY_ALLOC_SIZE = 4096;
const size_t MAX_BUFFER_COUNT = 3;
#ifdef DIRECTX_ENABLE_SEEK_TABLES
const size_t MAX_STREAMING_SEEK_PACKETS = 2048;
#endif
#ifdef DIRECTX_ENABLE_XMA2
const 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)
{
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<size_t>(XMA2_64KBLOCKINBYTES, buffer);
return buffer;
}
#else
UNREFERENCED_PARAMETER(tag);
#endif
buffer = AlignUp(buffer, MEMORY_ALLOC_SIZE);
buffer = std::max<size_t>(65536u, buffer);
return buffer;
}
static_assert(MEMORY_ALLOC_SIZE >= DVD_SECTOR_SIZE, "Memory size should be larger than sector size");
static_assert(MEMORY_ALLOC_SIZE >= DVD_SECTOR_SIZE || (MEMORY_ALLOC_SIZE% DVD_SECTOR_SIZE) == 0, "Memory size should be multiples of sector size");
}
//======================================================================================
// 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),
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<WAVEFORMATEX*>(buff);
assert(mWaveBank != nullptr);
mBase.Initialize(engine, mWaveBank->GetFormat(index, wfx, sizeof(buff)), flags);
WaveBankReader::Metadata metadata = {};
(void)mWaveBank->GetPrivateData(index, &metadata, sizeof(metadata));
mOffsetBytes = metadata.offsetBytes;
mLengthInBytes = metadata.lengthBytes;
#ifdef DIRECTX_ENABLE_SEEK_TABLES
WaveBankSeekData seekData = {};
(void)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::exception("CreateEvent");
}
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)
{
(void)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<WAVEFORMATEX*>(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(_countof(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<int>(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<int>(mPackets[k].state)]);
}
DebugTrace("]\n");
#endif
ThrowIfFailed(ReadBuffers());
break;
case WAIT_FAILED:
throw std::exception("WaitForMultipleObjects");
}
}
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;
size_t mCurrentPosition;
size_t mOffsetBytes;
size_t mLengthInBytes;
size_t mPacketSize;
size_t mTotalSize;
std::unique_ptr<uint8_t[], virtual_deleter> 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<uint8_t[], apu_deleter> 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;
uint32_t tag = GetFormatTag(wfx);
size_t packetSize = ComputeAsyncPacketSize(wfx, tag);
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<size_t>(wfx->nBlockAlign, DVD_SECTOR_SIZE);
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<UINT32>(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<uint8_t*>(xmaMemory));
}
else
#endif
{
mStreamBuffer.reset(reinterpret_cast<uint8_t*>(
VirtualAlloc(nullptr, static_cast<SIZE_T>(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<size_t>(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();
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 cbValid = static_cast<uint32_t>(std::min(mPacketSize, mLengthInBytes - mCurrentPosition));
mPackets[entry].valid = cbValid;
mPackets[entry].audioBytes = 0;
mPackets[entry].startPosition = static_cast<uint32_t>(mCurrentPosition);
mPackets[entry].request.Offset = static_cast<DWORD>(mOffsetBytes + mCurrentPosition);
if (!ReadFile(async, mPackets[entry].buffer, uint32_t(mPacketSize), nullptr, &mPackets[entry].request))
{
DWORD error = GetLastError();
if (error != ERROR_IO_PENDING)
{
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();
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)
BOOL result = GetOverlappedResultEx(async, &mPackets[j].request, &cb, 0, FALSE);
#else
BOOL result = GetOverlappedResult(async, &mPackets[j].request, &cb, FALSE);
#endif
if (result)
{
mPackets[j].state = State::READY;
}
else
{
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;
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.
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;
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;
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<Impl>(engine, waveBank, index, flags))
{
}
// Move constructor.
SoundStreamInstance::SoundStreamInstance(SoundStreamInstance&& moveFrom) noexcept
: pImpl(std::move(moveFrom.pImpl))
{
}
// Move assignment.
SoundStreamInstance& SoundStreamInstance::operator= (SoundStreamInstance&& moveFrom) noexcept
{
pImpl = std::move(moveFrom.pImpl);
return *this;
}
// 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 AudioListener& listener, const AudioEmitter& emitter, bool rhcoords)
{
pImpl->mBase.Apply3D(listener, emitter, rhcoords);
}
// Public accessors.
bool SoundStreamInstance::IsLooped() const noexcept
{
return pImpl->mLooped;
}
SoundState SoundStreamInstance::GetState() noexcept
{
SoundState state = pImpl->mBase.GetState(pImpl->mEndStream);
if (state == STOPPED)
{
pImpl->mPlaying = false;
}
return state;
}
IVoiceNotify* SoundStreamInstance::GetVoiceNotify() const noexcept
{
return pImpl.get();
}