/*
	Rogue Trader.
	Copyright (c) 2007, Darragh Coy.
	All rights reserved.
	Original work(s), source code & author(s) details available at: http://www.darraghcoy.com

	Redistribution and use in source and binary forms, with or without modification, are permitted 
	provided that the following conditions are met:

		* 	Redistributions of source code must retain the above copyright notice, this list of 
			conditions and the following disclaimer.
		* 	Redistributions in binary form must reproduce the above copyright notice, this list of 
			conditions and the following disclaimer in the documentation.
		* 	Neither the name of the author nor the names of contributors may be used to endorse or 
			promote products derived from this software without specific prior written permission.
		* 	The use of any third party libraries, modules or data files provided with the source 
			form of the program must follow and respect the individual licensing terms of these 
			components. You do not have permission to modify or circumvent the licensing terms for 
			provided third party components unless the third party licenses specifically allow this. 
			If you do not agree to the terms of provided third party components then you do not have 
			permission to use them in your derived work(s).
		* 	The terms of this license do not apply to separate (but related) distributions if supplied 
			by the author(s), only to the contents of this source distribution, derived works, and the 
			binaries produced by either modified or unmodified versions of the source code. The terms 
			of this license do not also apply to any third party libraries or components provided with 
			the source distribution. Such third party elements are licensed under their own separate 
			agreements.

	THIS SOFTWARE IS PROVIDED BY DARRAGH COY ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
	BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
	ARE DISCLAIMED. IN NO EVENT SHALL DARRAGH COY BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
	OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
	OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
	IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

//##############################################################################
//##############################################################################
//##############################################################################
//##############################################################################
//
//
// Class Name:   Sound
// Purpose:      Class that stores a sound file and has functions to play back
//               that sound. Can only be created by the sound system.
//
//
//##############################################################################
//##############################################################################
//##############################################################################
//##############################################################################

#include "Sound.h"

/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Name:     Sound::Sound()
Purpose:  Creates the sound object and loads the sound file with direct sound.
Params:   Pointer to the direct sound object, name of sound file to load
Returns:  n/a

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/

Sound::Sound( IDirectSound8 * directSound , const char * soundFile )
{
    // Abort if either the direct sound object or the sound file are null:

    if ( directSound == 0 || soundFile == 0 )
    {
        buffer = 0; return;
    }

    // Macros for error checking and abortion:

    #define ABORT_LOAD      { fclose(file); buffer = 0; return; }
    #define EVERYTHING_OK   ( ( ferror(file) | feof(file) ) == 0 )

    //--------------------------------------------------------------------------
    // Open the wave file
    //--------------------------------------------------------------------------

    // Try and open the given sound file:

    FILE * file = 0; fopen_s(&file,soundFile,"rb");

    // IF the sound file is not open then abort:

    if ( file == 0 ){ buffer = 0; return; }

    //--------------------------------------------------------------------------
    // Read the wave file header
    //--------------------------------------------------------------------------

    // Hold header info here:

    WaveFileHeader fileHeader;

    // Read the header from the file:

    fread( &fileHeader , sizeof(WaveFileHeader) , 1 , file );

    // Make sure reading went ok:

    if ( ! EVERYTHING_OK  ) ABORT_LOAD;

    //--------------------------------------------------------------------------
    // Validate wave file header contents:
    //--------------------------------------------------------------------------

    // Make sure file ID is ok:

    if ( fileHeader.riffIdentifier[0] != 'R' ) ABORT_LOAD;
    if ( fileHeader.riffIdentifier[1] != 'I' ) ABORT_LOAD;
    if ( fileHeader.riffIdentifier[2] != 'F' ) ABORT_LOAD;
    if ( fileHeader.riffIdentifier[3] != 'F' ) ABORT_LOAD;

    // Make sure this type of media file is a wave file:

    if ( fileHeader.waveIdentifier[0] != 'W' ) ABORT_LOAD;
    if ( fileHeader.waveIdentifier[1] != 'A' ) ABORT_LOAD;
    if ( fileHeader.waveIdentifier[2] != 'V' ) ABORT_LOAD;
    if ( fileHeader.waveIdentifier[3] != 'E' ) ABORT_LOAD;

    // Make sure the format information identifier is ok:

    if ( fileHeader.formatIdentifier[0] != 'f' ) ABORT_LOAD;
    if ( fileHeader.formatIdentifier[1] != 'm' ) ABORT_LOAD;
    if ( fileHeader.formatIdentifier[2] != 't' ) ABORT_LOAD;
    if ( fileHeader.formatIdentifier[3] != ' ' ) ABORT_LOAD;

    // Make sure format info is ok:

    if ( fileHeader.formatType       != 1  ) ABORT_LOAD;    // 1 = PCM WAVE FILE
    if ( fileHeader.samplesPerSecond == 0  ) ABORT_LOAD;
    if ( fileHeader.bytesPerSecond   == 0  ) ABORT_LOAD;
    if ( fileHeader.blockAlign       == 0  ) ABORT_LOAD;
    if ( fileHeader.bitsPerSample    == 0  ) ABORT_LOAD;

    if ( fileHeader.numChannels != 1 && fileHeader.numChannels != 2 ) ABORT_LOAD;

    // If the format data lenght is greater than 16 then skip over the rest of the format data:

    if ( fileHeader.formatDataLenght > 16 )
    {
        // Skip over the rest of the format data

        fseek( file , fileHeader.formatDataLenght - 16 , SEEK_CUR );

        // Abort loading if everything is not honky dorey:

        if ( ! EVERYTHING_OK  ) ABORT_LOAD;
    }

    //--------------------------------------------------------------------------
    // Keep reading chunks until the data chunk is found:
    //--------------------------------------------------------------------------

    unsigned char   chunkID[4]  = {0};  // 4 byte string identifying the current chunk
    unsigned int    chunkSize   = 0;    // size of the current chunk
    unsigned char * chunkData   = 0;    // pointer to the data of the chunk
    
    // False until we find the data chunk:
    
    bool found_data_chunk = false;

    // Keep scanning the file for the data chunk

    while ( found_data_chunk == false )
    {
        // Read the chunk ID: abort on failure

        fread( chunkID , 1 , 4 , file );    if ( ! EVERYTHING_OK  ) ABORT_LOAD;

        // Read the size of the chunk: abort on failure:

        fread( &chunkSize , 4 , 1 , file ); if ( ! EVERYTHING_OK  ) ABORT_LOAD;

        // See if the chunk id is the data chunk identifier:

        if
        (
            chunkID[0] == 'd' &&
            chunkID[1] == 'a' &&
            chunkID[2] == 't' &&
            chunkID[3] == 'a'
        )
        {
            // Found the data chunk: make sure the size of this chunk is not zero!

            if ( chunkSize == 0  ) ABORT_LOAD;

            // Allocate memory for reading the chunk data:

            chunkData = new unsigned char[ chunkSize ];

            // Read the chunk data:

            fread( chunkData , 1 , chunkSize , file );

            // If there was an error then free up the allocated chunk data and abort:

            if ( ! EVERYTHING_OK )
            {
                // Free the chunk data block:

                delete[] chunkData;

                // Abort any further loading:

                ABORT_LOAD;
            }

            // Abort scanning for the data chunk: has been found

            found_data_chunk = true;
        }

        // Haven't found the data chunk: skip over the data of this unknown chunk
        
        if ( found_data_chunk == false )
        {
			fseek( file , chunkSize , SEEK_CUR ); if ( ! EVERYTHING_OK  ) ABORT_LOAD;
		}
    }

    //--------------------------------------------------------------------------
    // Create a secondary buffer with direct sound to hold this sound:
    //--------------------------------------------------------------------------

    // Fill in a wave format structure first:

    WAVEFORMATEX waveFormat = {0};

    waveFormat.wFormatTag       = fileHeader.formatType;        // fill in the format type
    waveFormat.nChannels        = fileHeader.numChannels;       // fill in the number of channels
    waveFormat.nSamplesPerSec   = fileHeader.samplesPerSecond;  // fill in the number of samples per second
    waveFormat.nAvgBytesPerSec  = fileHeader.bytesPerSecond;    // fill in the number of bytes per second
    waveFormat.nBlockAlign      = fileHeader.blockAlign;        // fill in the sample size
    waveFormat.wBitsPerSample   = fileHeader.bitsPerSample;     // fill in the number of bits per sample
    waveFormat.cbSize           = 0;                            // extra format info bytes: 0

    // Create a buffer description object:

    DSBUFFERDESC bufferDescription = {0};

    bufferDescription.dwSize            = sizeof(DSBUFFERDESC);    // size of this structure
    bufferDescription.dwFlags          |= DSBCAPS_CTRLFREQUENCY;   // flags: allow frequency control
    bufferDescription.dwFlags          |= DSBCAPS_CTRLVOLUME;      // flags: allow volume control
    bufferDescription.dwFlags          |= DSBCAPS_CTRLPAN;         // flags: allow panning of the sound
    bufferDescription.dwBufferBytes     = chunkSize;               // number of bytes in the buffer
    bufferDescription.lpwfxFormat       = &waveFormat;             // pointer to wave format data

    // Now create the secondary buffer for this object:

    directSound->CreateSoundBuffer
    (
        & bufferDescription,                // pointer to buffer description struct
        (IDirectSoundBuffer**) & buffer,    // pointer to pointer which recieves buffer pointer
        0                                   // must be null
    );

    // If the creation was successful then fill the buffer with data:

    if ( buffer )
    {
        // Save a pointer to the direct sound buffer data here:

        unsigned char * bufferBytes = 0;

        // Save the amount of bytes given by direct sound for this sound here

        unsigned int allocatedSize = 0;

        // Try to lock down the created buffer to gain access to its internal byte buffer:

        int result = buffer->Lock
        (
            (DWORD)     0,                  // ???
            (DWORD)     chunkSize,          // number of bytes we want in the buffer
            (void**)    &bufferBytes,       // pointer which will receive a pointer to direct sound buffer bytes
            (DWORD*)    &allocatedSize,     // where to save the actual amount of bytes given by direct sound for this sound
            (void**)    0,                  // ???
            (DWORD*)    0,                  // ???
            (DWORD)     0                   // ???
        );

        // If the lock was successful then copy the sound data into the buffer:

        if ( result == 0 )
        {
            // Copy the data from the wave file into the direct sound buffer:

            memcpy( bufferBytes , chunkData , allocatedSize );

            // Unlock the buffer

            buffer->Unlock
            (
                bufferBytes,    // pointer to the buffer bytes
                allocatedSize,  // allocated size of the buffer
                0,              // ???
                0               // ???
            );
        }

    }   // end if direct sound buffer was created successfully

    //--------------------------------------------------------------------------
    // Cleanup:
    //--------------------------------------------------------------------------

    // Free up the allocated data buffer:
    
    if ( chunkData != 0 )
	{
		delete[] chunkData;
	}

    // Close the file

    fclose(file);

    // Undo these macros

    #undef ABORT_LOAD
    #undef EVERYTHING_OK
}

/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Name:     Sound::~Sound()
Purpose:  Destructor. Frees up the sound loaded if it exists.
Params:   n/a
Returns:  n/a

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/

Sound::~Sound()
{
    // If there is a sound buffer then release it:

    if ( buffer ) buffer->Release();
}
