//--------------------------------------------------------------------------------------
// 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();
}
