//-----------------------------------------------------------------------------
// File: WaveBank.cpp
//
// Desc: This sample demonstrates how to use the SetPlayRegion and 
//       SetFormat APIs to implement a wave bank of audio data by packaging
//       individual wave files together using XACT.  Note that this does not
//       use the XACT runtime engine - just DirectSound.
//
// Hist: 11.13.01 - New for December XDK
//       03.06.02 - Added audio level meters for April 02 XDK release
//       03.13.03 - Updated to use XACT-generated wave banks instead of 
//                      obsolete wave bundler wave banks
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#include <XBApp.h>
#include <XBFont.h>
#include <XBHelp.h>
#include <XBSound.h>
#include <xgraphics.h>
#include <assert.h>
#include <dsstdfx.h>
#include <xactwb.h>
#include "XactSounds.h" // This header file is generated by XACT


//-----------------------------------------------------------------------------
// Callouts for labelling the gamepad on the help screen
//-----------------------------------------------------------------------------
XBHELP_CALLOUT g_HelpCallouts[] = 
{
    { XBHELP_BACK_BUTTON,  XBHELP_PLACEMENT_1, L"Display help" },
    { XBHELP_DPAD,         XBHELP_PLACEMENT_1, L"Select sound" },
    { XBHELP_A_BUTTON,     XBHELP_PLACEMENT_1, L"Play sound" },
    { XBHELP_X_BUTTON,     XBHELP_PLACEMENT_1, L"Loop sound" },
    { XBHELP_B_BUTTON,     XBHELP_PLACEMENT_1, L"Stop buffer" },
};

const DWORD NUM_HELP_CALLOUTS = sizeof(g_HelpCallouts) / sizeof(g_HelpCallouts[0]);




const DWORD DISPLAY_LENGTH = 7;




//-----------------------------------------------------------------------------
// Name: BufferQueueNode
// Desc: Node in our queue of buffers
//-----------------------------------------------------------------------------
struct BufferQueueNode
{
    BufferQueueNode*    pNext;
    LPDIRECTSOUNDBUFFER pBuffer;
    LONG                lPlayingSound;
};



// Helper function for converting wave formats
VOID CreateWaveFormatEx( WAVEBANKMINIWAVEFORMAT* pmini, XBOXADPCMWAVEFORMAT* pwfx );


//-----------------------------------------------------------------------------
// Name: class CXBoxSample
// Desc: Main class to run this application. Most functionality is inherited
//       from the CXBApplication base class.
//-----------------------------------------------------------------------------
class CXBoxSample 
{
public:
    HRESULT LoadWaveBank( LPCSTR strFilename );             // Load a wave bank
    HRESULT InitializeBufferPool( DWORD dwNumberOfBuffers,  // Initialize our buffer pool
                                  VOID* pbSampleData, 
                                  DWORD dwDataLength );
    HRESULT PlaySound( DWORD dwIndex, BOOL fLooping );      // Play the specified sound

    BufferQueueNode* Dequeue();                             // Dequeue from buffer pool
    VOID             Enqueue( BufferQueueNode* pNode );     // Enqueue to buffer pool

    //CXBFont     m_Font;                                     // Font object
    //CXBHelp     m_Help;                                     // Help object
    BOOL        m_bDrawHelp;                                // TRUE to draw help screen

    LPDIRECTSOUND       m_pDSound;                          // DirectSound object

    DWORD               m_dwDisplayStart;                   // Display management
    DWORD               m_dwSelection;                      // ...
    DWORD               m_dwCurrent;                        // ...

    // Wave bank entries and sample data
    WAVEBANKENTRY       m_aWaveBankEntries[XACT_WAVEBANK_SOUNDS_ENTRY_COUNT];
    BYTE*               m_pbSampleData;
    DWORD               m_dwWaveNameSize;
    BYTE*               m_pbWaveNames;

    BufferQueueNode*    m_pNodes;                           // Buffer Queue allocation
    DWORD               m_dwNumberOfBuffers;                // Total # of buffers
    DWORD               m_dwAvailableBuffers;               // # of available buffers
    DWORD               m_dwPlayingBuffers;                 // # of playing buffers
    BufferQueueNode*    m_pBufferPoolHead;                  // Queue of ready buffers
    BufferQueueNode*    m_pBufferPoolTail;                  // ...

    BufferQueueNode*    m_pBusyBuffersHead;                 // List of busy buffers

public:
    virtual HRESULT Initialize();
    virtual HRESULT Render();
    virtual HRESULT FrameMove();

    CXBoxSample();
};




//-----------------------------------------------------------------------------
// Name: Dequeue()
// Desc: Dequeues from the head of the buffer queue, returning NULL if empty
//-----------------------------------------------------------------------------
BufferQueueNode* CXBoxSample::Dequeue()
{
    BufferQueueNode* pNode = m_pBufferPoolHead;

    if( m_pBufferPoolHead )
    {
        m_pBufferPoolHead = m_pBufferPoolHead->pNext;
        if( m_pBufferPoolTail == pNode )
            m_pBufferPoolTail = NULL;
    }

    return pNode;
}




//-----------------------------------------------------------------------------
// Name: Enqueue()
// Desc: Adds to the tail of the queue
//-----------------------------------------------------------------------------
void CXBoxSample::Enqueue( BufferQueueNode* pNode )
{
    pNode->pNext = NULL;
    if( m_pBufferPoolTail )
    {
        m_pBufferPoolTail->pNext = pNode;
        m_pBufferPoolTail = pNode;
    }
    else
    {
        m_pBufferPoolHead = pNode;
        m_pBufferPoolTail = pNode;
    }
}




//-----------------------------------------------------------------------------
// Name: main()
// Desc: Entry point to the program.
//-----------------------------------------------------------------------------
/*
VOID __cdecl main()
{
    CXBoxSample xbApp;
    if( FAILED( xbApp.Create() ) )
        return;
    xbApp.Run();
}
*/


//-----------------------------------------------------------------------------
// Name: CXBoxSample()
// Desc: Constructor for CXBoxSample class
//-----------------------------------------------------------------------------
CXBoxSample::CXBoxSample() 
            
{
    m_bDrawHelp         = FALSE;
    m_pbSampleData      = NULL;
    m_pDSound           = NULL;

    m_pNodes            = NULL;
    m_pBufferPoolHead   = NULL;
    m_pBufferPoolTail   = NULL;
    m_pBusyBuffersHead  = NULL;
    m_pbWaveNames       = NULL;

    m_dwCurrent         = 0;
    m_dwDisplayStart    = 0;
    m_dwSelection       = 0;
}




//-----------------------------------------------------------------------------
// Name: InitializeBufferPool()
// Desc: Initializes the buffer pool by creating the specified number of 
//       buffers, inserting them into a queue, and mapping the wave bank
//       data
//-----------------------------------------------------------------------------
HRESULT CXBoxSample::InitializeBufferPool( DWORD dwNumberOfBuffers, VOID* pbSampleData, DWORD dwDataLength )
{
    // Set up a default wave format
    WAVEFORMATEX wfx    = {0};
    wfx.wFormatTag      = WAVE_FORMAT_PCM;
    wfx.nChannels       = 1;
    wfx.nSamplesPerSec  = 44100;
    wfx.wBitsPerSample  = 16;
    wfx.nBlockAlign     = wfx.nChannels * wfx.wBitsPerSample / 8 ;
    wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;

    // And a default buffer description
    DSBUFFERDESC dsbd   = {0};
    dsbd.dwBufferBytes  = 0;
    dsbd.lpwfxFormat    = &wfx;

    // Allocate a block of Buffer Queue nodes
    m_pNodes = new BufferQueueNode[ dwNumberOfBuffers ];
    if( !m_pNodes )
    {
        return E_OUTOFMEMORY;
    }
    m_dwNumberOfBuffers = dwNumberOfBuffers;
    ZeroMemory( m_pNodes, m_dwNumberOfBuffers * sizeof( BufferQueueNode ) );

    // Set up the initial queue
    for( DWORD i = 0; i < m_dwNumberOfBuffers; i++ )
    {
        if( FAILED( DirectSoundCreateBuffer( &dsbd, &m_pNodes[i].pBuffer ) ) )
        {
            // TODO: Should clean up already-created buffers
            return E_OUTOFMEMORY;
        }
        m_pNodes[i].pBuffer->SetBufferData( pbSampleData, dwDataLength );
        m_pNodes[i].pNext = &m_pNodes[ i + 1 ];
    }

    // Set up our pointers
    m_pBufferPoolHead           = &m_pNodes[ 0 ];
    m_pBufferPoolTail           = &m_pNodes[ m_dwNumberOfBuffers - 1 ];
    m_pBufferPoolTail->pNext    = NULL;


    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: LoadWaveBank()
// Desc: Loads a wave bank
//-----------------------------------------------------------------------------
HRESULT CXBoxSample::LoadWaveBank( LPCSTR strFilename )
{
    // First, open the file
    HANDLE hWaveBank;
    hWaveBank = CreateFile( strFilename, 
                            GENERIC_READ, 
                            FILE_SHARE_READ, 
                            NULL, 
                            OPEN_EXISTING, 
                            FILE_ATTRIBUTE_NORMAL, 
                            NULL );
    if( hWaveBank == INVALID_HANDLE_VALUE )
    {
        OUTPUT_DEBUG_STRING( "Sound bank not found!\n" );
        return XBAPPERR_MEDIANOTFOUND;
    }

    // Read the header
    WAVEBANKHEADER  header;
    DWORD           dwRead;
    if( !ReadFile( hWaveBank, &header, sizeof( WAVEBANKHEADER ), &dwRead, NULL ) )
    {
        CloseHandle( hWaveBank );
        return E_FAIL;
    }

    // Validate the signature - note that this sample just shows
    // how to load standard wave banks.  Streaming wave banks are
    // handled by the WaveBankStream sample, and compact wave banks
    // are not handled at all yet
    if( header.dwSignature  != WAVEBANK_HEADER_SIGNATURE ||
        header.dwVersion    != WAVEBANK_HEADER_VERSION )
    {
        CloseHandle( hWaveBank );
        return E_UNEXPECTED;
    }

    // Read the WAVEBANKDATA struct - this contains useful meta data about
    // the wave bank
    DWORD dwOffset = header.Segments[ WAVEBANK_SEGIDX_BANKDATA ].dwOffset;
    DWORD dwLength = header.Segments[ WAVEBANK_SEGIDX_BANKDATA ].dwLength;
    WAVEBANKDATA waveBankData;
    SetFilePointer( hWaveBank, dwOffset, NULL, FILE_BEGIN );
    if( !ReadFile( hWaveBank, &waveBankData, dwLength, &dwRead, NULL ) )
    {
        CloseHandle( hWaveBank );
        return E_UNEXPECTED;
    }

    // Make sure we got the number of waves we were expecting
    if( waveBankData.dwEntryCount != XACT_WAVEBANK_SOUNDS_ENTRY_COUNT )
    {
        CloseHandle( hWaveBank );
        return E_UNEXPECTED;
    }

    // Read the WAVEBANKENTRY data - this is where the details of each 
    // wave file is stored
    dwOffset = header.Segments[ WAVEBANK_SEGIDX_ENTRYMETADATA ].dwOffset;
    dwLength = header.Segments[ WAVEBANK_SEGIDX_ENTRYMETADATA ].dwLength;
    assert( dwLength == sizeof( m_aWaveBankEntries ) );
    SetFilePointer( hWaveBank, dwOffset, NULL, FILE_BEGIN );
    if( !ReadFile( hWaveBank, m_aWaveBankEntries, dwLength, &dwRead, NULL ) )
    {
        CloseHandle( hWaveBank );
        return E_UNEXPECTED;
    }

    // Allocate a buffer to store all the wave data
    dwOffset = header.Segments[ WAVEBANK_SEGIDX_ENTRYWAVEDATA ].dwOffset;
    dwLength = header.Segments[ WAVEBANK_SEGIDX_ENTRYWAVEDATA ].dwLength;
    m_pbSampleData = new BYTE[ dwLength ];
    if( !m_pbSampleData )
    {
        CloseHandle( hWaveBank );
        return E_OUTOFMEMORY;
    }

    // Read the the actual wave data
    SetFilePointer( hWaveBank, dwOffset, NULL, FILE_BEGIN );
    if( !ReadFile( hWaveBank, m_pbSampleData, dwLength, &dwRead, NULL ) )
    {
        CloseHandle( hWaveBank );
        return E_UNEXPECTED;
    }

    // Initialize a buffer pool on this wave bank
    if( FAILED( InitializeBufferPool( 64, m_pbSampleData, dwLength ) ) )
    {
        CloseHandle( hWaveBank );
        return E_OUTOFMEMORY;
    }

    // If the wave bank contains names for the waves, load them up so
    // we can display them on screen
    if( waveBankData.dwFlags & WAVEBANK_FLAGS_ENTRYNAMES )
    {
        dwOffset = header.Segments[ WAVEBANK_SEGIDX_ENTRYNAMES ].dwOffset;
        dwLength = header.Segments[ WAVEBANK_SEGIDX_ENTRYNAMES ].dwLength;

        m_dwWaveNameSize = waveBankData.dwEntryNameElementSize;
        m_pbWaveNames = new BYTE[ dwLength ];
        if( m_pbWaveNames )
        {
            SetFilePointer( hWaveBank, dwOffset, NULL, FILE_BEGIN );
            ReadFile( hWaveBank, m_pbWaveNames, dwLength, &dwRead, NULL );
        }
    }

    CloseHandle( hWaveBank );

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: PlaySound()
// Desc: Grabs the first available buffer and plays the specified sound from
//       the wave bank on that buffer
//-----------------------------------------------------------------------------
HRESULT CXBoxSample::PlaySound( DWORD dwIndex, BOOL fLooping )
{
    // Grab the node off the head of the queue
    BufferQueueNode* pNode = Dequeue();
    if( !pNode )
    {
        return E_PENDING;
    }

    // Add this node to the busy list
    pNode->pNext = m_pBusyBuffersHead;
    m_pBusyBuffersHead = pNode;

#if _DEBUG
    // Make sure this buffer is really available
    DWORD dwStatus;
    HRESULT hr = pNode->pBuffer->GetStatus( &dwStatus );
    assert( SUCCEEDED( hr ) && !( dwStatus & DSBSTATUS_PLAYING ) );
#endif // _DEBUG

    // We're using a XBOXADPCMWAVEFORMAT structure to guarantee we have enough
    // space for a PCM or ADPCM WAVEFORMATEX struct.  This way, we don't have
    // to make another call to find the size, allocate memory on the heap, and
    // then free it later,
    XBOXADPCMWAVEFORMAT wfx;
    CreateWaveFormatEx( &m_aWaveBankEntries[ dwIndex ].Format, &wfx );
    pNode->pBuffer->SetFormat( (WAVEFORMATEX *)&wfx );

    // Set loop and play regions...
    pNode->pBuffer->SetPlayRegion( m_aWaveBankEntries[ dwIndex ].PlayRegion.dwOffset,
                                   m_aWaveBankEntries[ dwIndex ].PlayRegion.dwLength );
    pNode->pBuffer->SetLoopRegion( m_aWaveBankEntries[ dwIndex ].LoopRegion.dwOffset, 
                                   m_aWaveBankEntries[ dwIndex ].LoopRegion.dwLength );

    // Play the sound
    pNode->lPlayingSound = dwIndex;
    if( FAILED( pNode->pBuffer->PlayEx( 0, DSBPLAY_FROMSTART | ( fLooping ? DSBPLAY_LOOPING : 0 ) ) ) )
        return E_FAIL;

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: Initialize()
// Desc: Performs initialization
//-----------------------------------------------------------------------------
HRESULT CXBoxSample::Initialize()
{
    
    DirectSoundUseFullHRTF();

    // download the standard DirectSound effects image
    DSEFFECTIMAGELOC EffectLoc;
    EffectLoc.dwI3DL2ReverbIndex = GraphI3DL2_I3DL2Reverb;
    EffectLoc.dwCrosstalkIndex   = GraphXTalk_XTalk;
    if( FAILED( XAudioDownloadEffectsImage( "d:\\media\\dsstdfx.bin", 
                                            &EffectLoc, 
                                            XAUDIO_DOWNLOADFX_EXTERNFILE, 
                                            NULL ) ) )
        return E_FAIL;

    // Load our wave bank
    if( FAILED( LoadWaveBank( "d:\\media\\XactSounds_memory.xwb" ) ) )
        return XBAPPERR_MEDIANOTFOUND;
    
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: FrameMove()
// Desc: Performs per-frame updates
//-----------------------------------------------------------------------------
HRESULT CXBoxSample::FrameMove()
{
	/*
    // Toggle help
    if( m_DefaultGamepad.wPressedButtons & XINPUT_GAMEPAD_BACK ) 
    {
        m_bDrawHelp = !m_bDrawHelp;
    }

    // A to trigger the sound as a one-shot
    if( m_DefaultGamepad.bPressedAnalogButtons[ XINPUT_GAMEPAD_A ] )
    {
        PlaySound( m_dwCurrent, FALSE );
    }

    // X to loop the sound
    if( m_DefaultGamepad.bPressedAnalogButtons[ XINPUT_GAMEPAD_X ] )
    {
        PlaySound( m_dwCurrent, TRUE );
    }

    // B to stop the selected playing sound
    if( m_DefaultGamepad.bPressedAnalogButtons[ XINPUT_GAMEPAD_B ] &&
        m_dwSelection < m_dwPlayingBuffers )
    {
        BufferQueueNode* pNode = m_pBusyBuffersHead;
        for( DWORD i = 0; i < m_dwSelection; i++ )
            pNode = pNode->pNext;

        pNode->pBuffer->Stop();
    }
	*/

    // Walk the busy buffers list, and remove any buffer that is done
    // playing
    BufferQueueNode** ppNode;
    m_dwPlayingBuffers = 0;
    for( ppNode = &m_pBusyBuffersHead; (*ppNode); )
    {
        DWORD dwStatus;
        (*ppNode)->pBuffer->GetStatus( &dwStatus );

        // If the buffer is no longer playing
        if( !( dwStatus & DSBSTATUS_PLAYING ) )
        {
            // Remove it from the list and add to the queue
            BufferQueueNode* pRemove = *ppNode;
            (*ppNode) = (*ppNode)->pNext;
            Enqueue( pRemove );
        }
        else
        {
            ++m_dwPlayingBuffers;
            ppNode = &(*ppNode)->pNext;
        }
    }

    m_dwAvailableBuffers = m_dwNumberOfBuffers - m_dwPlayingBuffers;

    // If our selection is off the end of the list, correct that
    if( m_dwSelection > m_dwPlayingBuffers - 1 )
        m_dwSelection = m_dwPlayingBuffers - 1;


	/*
    // Select sound
    if( m_DefaultGamepad.wPressedButtons & XINPUT_GAMEPAD_DPAD_LEFT )
    {
        m_dwCurrent = ( m_dwCurrent + XACT_WAVEBANK_SOUNDS_ENTRY_COUNT - 1 ) % XACT_WAVEBANK_SOUNDS_ENTRY_COUNT;
    }
    else if( m_DefaultGamepad.wPressedButtons & XINPUT_GAMEPAD_DPAD_RIGHT )
    {
        m_dwCurrent = ( m_dwCurrent + 1 ) % XACT_WAVEBANK_SOUNDS_ENTRY_COUNT;
    }
    else if( m_DefaultGamepad.wPressedButtons & XINPUT_GAMEPAD_DPAD_DOWN )
    {
        if( m_dwSelection < m_dwPlayingBuffers - 1 )
            m_dwSelection++;
    }
    else if( m_DefaultGamepad.wPressedButtons & XINPUT_GAMEPAD_DPAD_UP )
    {
        if( m_dwSelection > 0 )
            m_dwSelection--;
    }
	*/

    // If we've got a list of playing sounds, we want to manage it so that
    // we're always showing a full screen that includes our selection
    if( m_dwPlayingBuffers )
    {
        if( m_dwPlayingBuffers < DISPLAY_LENGTH )
            m_dwDisplayStart = 0;
        else if( m_dwPlayingBuffers - m_dwDisplayStart < DISPLAY_LENGTH  )
            m_dwDisplayStart = m_dwPlayingBuffers - ( DISPLAY_LENGTH  );

        // make sure the selection is always on screen
        if( m_dwSelection < m_dwDisplayStart )
            m_dwDisplayStart = m_dwSelection;
        if( m_dwSelection - m_dwDisplayStart > DISPLAY_LENGTH - 1 )
            m_dwDisplayStart = m_dwSelection - ( DISPLAY_LENGTH - 1 );
    }

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: Render()
// Desc: Renders the scene
//-----------------------------------------------------------------------------
HRESULT CXBoxSample::Render()
{
 
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CreateWaveFormatEx()
// Desc: Helper function to populate an XBOXADPCMWAVEFORMAT struct, based off
//       the packed wave format struct in the wave bank
//-----------------------------------------------------------------------------
VOID CreateWaveFormatEx( WAVEBANKMINIWAVEFORMAT* pmini, XBOXADPCMWAVEFORMAT* pwfx )
{
    assert( pmini && pwfx );
    ZeroMemory( pwfx, sizeof( XBOXADPCMWAVEFORMAT ) );

    // Common to both PCM and ADPCM
    pwfx->wfx.nChannels = pmini->nChannels;
    pwfx->wfx.nSamplesPerSec = pmini->nSamplesPerSec;

    // Deal with PCM/ADPCM-specific fields
    if( pmini->wFormatTag == WAVEBANKMINIFORMAT_TAG_ADPCM )
    {
        pwfx->wfx.wFormatTag = WAVE_FORMAT_XBOX_ADPCM;
        pwfx->wSamplesPerBlock = 64;
        pwfx->wfx.cbSize = sizeof( XBOXADPCMWAVEFORMAT ) - sizeof( WAVEFORMATEX );

        pwfx->wfx.wBitsPerSample  = 4;
        pwfx->wfx.nBlockAlign     = pwfx->wfx.nChannels * 36;
        pwfx->wfx.nAvgBytesPerSec = pwfx->wfx.nSamplesPerSec / pwfx->wSamplesPerBlock * pwfx->wfx.nBlockAlign;
    }
    else
    {
        pwfx->wfx.wFormatTag      = WAVE_FORMAT_PCM;
        pwfx->wfx.wBitsPerSample  = ( pmini->wBitsPerSample == WAVEBANKMINIFORMAT_BITDEPTH_8 ) ? 8 : 16;
        pwfx->wfx.nBlockAlign     = pwfx->wfx.nChannels * pwfx->wfx.wBitsPerSample / 8;
        pwfx->wfx.nAvgBytesPerSec = pwfx->wfx.nSamplesPerSec * pwfx->wfx.nBlockAlign;
    }
}



CXBoxSample soundFX;


void updateEffect() {
	soundFX.FrameMove(); 
}

void playEffect() {
	soundFX.PlaySound(0, false);
}

void stopEffect() {
	//soundFX.s
}
void effectInit() {
	soundFX.Initialize(); 
}