/***************************************************************************
 *
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *
 *  File:       xact3wb.h
 *  Content:    XACT 3 wave bank definitions.
 *
 ****************************************************************************/

#ifndef __XACT3WB_H__
#define __XACT3WB_H__

#ifdef _XBOX
#   include <xtl.h>
#else
#   include <math.h>
#endif

#include <audiodefs.h>
#include <xma2defs.h>

#pragma warning(push)
#pragma warning(disable:4201)
#pragma warning(disable:4214)   // nonstandard extension used : bit field types other than int

#pragma pack(push, 1)
#if !defined(_X86_)
    #define XACTUNALIGNED __unaligned
#else
    #define XACTUNALIGNED
#endif

#ifdef _M_PPCBE
#pragma bitfield_order(push, lsb_to_msb)
#endif

#define WAVEBANK_HEADER_SIGNATURE               'DNBW'      // WaveBank  RIFF chunk signature
#define WAVEBANK_HEADER_VERSION                 44          // Current wavebank file version

#define WAVEBANK_BANKNAME_LENGTH                64          // Wave bank friendly name length, in characters
#define WAVEBANK_ENTRYNAME_LENGTH               64          // Wave bank entry friendly name length, in characters

#define WAVEBANK_MAX_DATA_SEGMENT_SIZE          0xFFFFFFFF  // Maximum wave bank data segment size, in bytes
#define WAVEBANK_MAX_COMPACT_DATA_SEGMENT_SIZE  0x001FFFFF  // Maximum compact wave bank data segment size, in bytes

typedef DWORD WAVEBANKOFFSET;

//
// Bank flags
//

#define WAVEBANK_TYPE_BUFFER         0x00000000      // In-memory buffer
#define WAVEBANK_TYPE_STREAMING      0x00000001      // Streaming
#define WAVEBANK_TYPE_MASK           0x00000001

#define WAVEBANK_FLAGS_ENTRYNAMES    0x00010000      // Bank includes entry names
#define WAVEBANK_FLAGS_COMPACT       0x00020000      // Bank uses compact format
#define WAVEBANK_FLAGS_SYNC_DISABLED 0x00040000      // Bank is disabled for audition sync
#define WAVEBANK_FLAGS_SEEKTABLES    0x00080000      // Bank includes seek tables.
#define WAVEBANK_FLAGS_MASK          0x000F0000

//
// Entry flags
//

#define WAVEBANKENTRY_FLAGS_READAHEAD       0x00000001  // Enable stream read-ahead
#define WAVEBANKENTRY_FLAGS_LOOPCACHE       0x00000002  // One or more looping sounds use this wave
#define WAVEBANKENTRY_FLAGS_REMOVELOOPTAIL  0x00000004  // Remove data after the end of the loop region
#define WAVEBANKENTRY_FLAGS_IGNORELOOP      0x00000008  // Used internally when the loop region can't be used
#define WAVEBANKENTRY_FLAGS_MASK            0x00000008

//
// Entry wave format identifiers
//

#define WAVEBANKMINIFORMAT_TAG_PCM      0x0     // PCM data
#define WAVEBANKMINIFORMAT_TAG_XMA      0x1     // XMA data
#define WAVEBANKMINIFORMAT_TAG_ADPCM    0x2     // ADPCM data
#define WAVEBANKMINIFORMAT_TAG_WMA      0x3     // WMA data

#define WAVEBANKMINIFORMAT_BITDEPTH_8   0x0     // 8-bit data (PCM only)
#define WAVEBANKMINIFORMAT_BITDEPTH_16  0x1     // 16-bit data (PCM only)

//
// Arbitrary fixed sizes
//
#define WAVEBANKENTRY_XMASTREAMS_MAX          3   // enough for 5.1 channel audio
#define WAVEBANKENTRY_XMACHANNELS_MAX         6   // enough for 5.1 channel audio (cf. XAUDIOCHANNEL_SOURCEMAX)

//
// DVD data sizes
//

#define WAVEBANK_DVD_SECTOR_SIZE    2048
#define WAVEBANK_DVD_BLOCK_SIZE     (WAVEBANK_DVD_SECTOR_SIZE * 16)

//
// Bank alignment presets
//

#define WAVEBANK_ALIGNMENT_MIN  4                           // Minimum alignment
#define WAVEBANK_ALIGNMENT_DVD  WAVEBANK_DVD_SECTOR_SIZE    // DVD-optimized alignment

//
// Wave bank segment identifiers
//

typedef enum WAVEBANKSEGIDX
{
    WAVEBANK_SEGIDX_BANKDATA = 0,       // Bank data
    WAVEBANK_SEGIDX_ENTRYMETADATA,      // Entry meta-data
    WAVEBANK_SEGIDX_SEEKTABLES,         // Storage for seek tables for the encoded waves.
    WAVEBANK_SEGIDX_ENTRYNAMES,         // Entry friendly names
    WAVEBANK_SEGIDX_ENTRYWAVEDATA,      // Entry wave data
    WAVEBANK_SEGIDX_COUNT
} WAVEBANKSEGIDX, *LPWAVEBANKSEGIDX;

typedef const WAVEBANKSEGIDX *LPCWAVEBANKSEGIDX;

//
// Endianness
//

#ifdef __cplusplus

namespace XACTWaveBank
{
    __inline void SwapBytes(XACTUNALIGNED DWORD &dw)
    {

#ifdef _X86_

        __asm
        {
            mov edi, dw
            mov eax, [edi]
            bswap eax
            mov [edi], eax
        }

#else // _X86_

        dw = _byteswap_ulong(dw);

#endif // _X86_

    }

    __inline void SwapBytes(XACTUNALIGNED WORD &w)
    {

#ifdef _X86_

        __asm
        {
            mov edi, w
            mov ax, [edi]
            xchg ah, al
            mov [edi], ax
        }

#else // _X86_

        w = _byteswap_ushort(w);

#endif // _X86_

    }

}

#endif // __cplusplus

//
// Wave bank region in bytes.
//

typedef struct WAVEBANKREGION
{
    DWORD       dwOffset;               // Region offset, in bytes.
    DWORD       dwLength;               // Region length, in bytes.

#ifdef __cplusplus

    void SwapBytes(void)
    {
        XACTWaveBank::SwapBytes(dwOffset);
        XACTWaveBank::SwapBytes(dwLength);
    }

#endif // __cplusplus

} WAVEBANKREGION, *LPWAVEBANKREGION;

typedef const WAVEBANKREGION *LPCWAVEBANKREGION;


//
// Wave bank region in samples.
//

typedef struct WAVEBANKSAMPLEREGION
{
    DWORD       dwStartSample;          // Start sample for the region.
    DWORD       dwTotalSamples;         // Region length in samples.

#ifdef __cplusplus

    void SwapBytes(void)
    {
        XACTWaveBank::SwapBytes(dwStartSample);
        XACTWaveBank::SwapBytes(dwTotalSamples);
    }

#endif // __cplusplus

} WAVEBANKSAMPLEREGION, *LPWAVEBANKSAMPLEREGION;

typedef const WAVEBANKSAMPLEREGION *LPCWAVEBANKSAMPLEREGION;


//
// Wave bank file header
//

typedef struct WAVEBANKHEADER
{
    DWORD           dwSignature;                        // File signature
    DWORD           dwVersion;                          // Version of the tool that created the file
    DWORD           dwHeaderVersion;                    // Version of the file format
    WAVEBANKREGION  Segments[WAVEBANK_SEGIDX_COUNT];    // Segment lookup table

#ifdef __cplusplus

    void SwapBytes(void)
    {
        XACTWaveBank::SwapBytes(dwSignature);
        XACTWaveBank::SwapBytes(dwVersion);
        XACTWaveBank::SwapBytes(dwHeaderVersion);

        for(int i = 0; i < WAVEBANK_SEGIDX_COUNT; i++)
        {
            Segments[i].SwapBytes();
        }
    }

#endif // __cplusplus

} WAVEBANKHEADER, *LPWAVEBANKHEADER;

typedef const WAVEBANKHEADER *LPCWAVEBANKHEADER;

//
// Table for converting WMA Average Bytes per Second values to the WAVEBANKMINIWAVEFORMAT wBlockAlign field
// NOTE: There can be a max of 8 values in the table.
//

#define MAX_WMA_AVG_BYTES_PER_SEC_ENTRIES 7

static const DWORD aWMAAvgBytesPerSec[] =
{
    12000,
    24000,
    4000,
    6000,
    8000,
    20000,
    2500
};
// bitrate = entry * 8

//
// Table for converting WMA Block Align values to the WAVEBANKMINIWAVEFORMAT wBlockAlign field
// NOTE: There can be a max of 32 values in the table.
//

#define MAX_WMA_BLOCK_ALIGN_ENTRIES 17

static const DWORD aWMABlockAlign[] =
{
    929,
    1487,
    1280,
    2230,
    8917,
    8192,
    4459,
    5945,
    2304,
    1536,
    1485,
    1008,
    2731,
    4096,
    6827,
    5462,
    1280
};

struct WAVEBANKENTRY;

//
// Entry compressed data format
//

typedef union WAVEBANKMINIWAVEFORMAT
{
    struct
    {
        DWORD       wFormatTag      : 2;        // Format tag
        DWORD       nChannels       : 3;        // Channel count (1 - 6)
        DWORD       nSamplesPerSec  : 18;       // Sampling rate
        DWORD       wBlockAlign     : 8;        // Block alignment.  For WMA, lower 6 bits block alignment index, upper 2 bits bytes-per-second index.
        DWORD       wBitsPerSample  : 1;        // Bits per sample (8 vs. 16, PCM only); WMAudio2/WMAudio3 (for WMA)
    };

    DWORD           dwValue;

#ifdef __cplusplus

    void SwapBytes(void)
    {
        XACTWaveBank::SwapBytes(dwValue);
    }

    WORD BitsPerSample() const
    {
        if (wFormatTag == WAVEBANKMINIFORMAT_TAG_XMA)
            return XMA_OUTPUT_SAMPLE_BITS; // First, because most common on Xbox 360
        if (wFormatTag == WAVEBANKMINIFORMAT_TAG_WMA)
            return 16;
        if (wFormatTag == WAVEBANKMINIFORMAT_TAG_ADPCM)
            return 4; // MSADPCM_BITS_PER_SAMPLE == 4

        // wFormatTag must be WAVEBANKMINIFORMAT_TAG_PCM (2 bits can only represent 4 different values)
        return (wBitsPerSample == WAVEBANKMINIFORMAT_BITDEPTH_16) ? 16 : 8;
    }

    #define ADPCM_MINIWAVEFORMAT_BLOCKALIGN_CONVERSION_OFFSET 22
    DWORD BlockAlign() const
    {
        DWORD dwReturn = 0;

        switch (wFormatTag)
        {
        case WAVEBANKMINIFORMAT_TAG_PCM:
            dwReturn = wBlockAlign;
            break;

        case WAVEBANKMINIFORMAT_TAG_XMA:
            dwReturn = nChannels * XMA_OUTPUT_SAMPLE_BITS / 8;
            break;

        case WAVEBANKMINIFORMAT_TAG_ADPCM:
            dwReturn = (wBlockAlign + ADPCM_MINIWAVEFORMAT_BLOCKALIGN_CONVERSION_OFFSET) * nChannels;
            break;

        case WAVEBANKMINIFORMAT_TAG_WMA:
            {
                DWORD dwBlockAlignIndex = wBlockAlign & 0x1F;
                if (dwBlockAlignIndex < MAX_WMA_BLOCK_ALIGN_ENTRIES)
                        dwReturn = aWMABlockAlign[dwBlockAlignIndex];
            }
            break;
        }

        return dwReturn;
    }

    DWORD AvgBytesPerSec() const
    {
        DWORD dwReturn = 0;

        switch (wFormatTag)
        {
        case WAVEBANKMINIFORMAT_TAG_PCM:
        case WAVEBANKMINIFORMAT_TAG_XMA:
            dwReturn = nSamplesPerSec * wBlockAlign;
            break;

        case WAVEBANKMINIFORMAT_TAG_ADPCM:
            {
                DWORD blockAlign = BlockAlign();
                DWORD samplesPerAdpcmBlock = AdpcmSamplesPerBlock();
                dwReturn = blockAlign * nSamplesPerSec / samplesPerAdpcmBlock;
            }
            break;

        case WAVEBANKMINIFORMAT_TAG_WMA:
            {
                DWORD dwBytesPerSecIndex = wBlockAlign >> 5;
                if (dwBytesPerSecIndex < MAX_WMA_AVG_BYTES_PER_SEC_ENTRIES)
                    dwReturn = aWMAAvgBytesPerSec[dwBytesPerSecIndex];
            }
            break;
        }

        return dwReturn;
    }

    DWORD EncodeWMABlockAlign(DWORD dwBlockAlign, DWORD dwAvgBytesPerSec) const
    {
        DWORD dwReturn = 0;
        DWORD dwBlockAlignIndex = 0;
        DWORD dwBytesPerSecIndex = 0;

        for (; dwBlockAlignIndex < MAX_WMA_BLOCK_ALIGN_ENTRIES && dwBlockAlign != aWMABlockAlign[dwBlockAlignIndex]; dwBlockAlignIndex++);

        if (dwBlockAlignIndex < MAX_WMA_BLOCK_ALIGN_ENTRIES)
        {
            for (; dwBytesPerSecIndex < MAX_WMA_AVG_BYTES_PER_SEC_ENTRIES && dwAvgBytesPerSec != aWMAAvgBytesPerSec[dwBytesPerSecIndex]; dwBytesPerSecIndex++);

            if (dwBytesPerSecIndex < MAX_WMA_AVG_BYTES_PER_SEC_ENTRIES)
            {
                dwReturn = dwBlockAlignIndex | (dwBytesPerSecIndex << 5);
            }
        }

        return dwReturn;
    }


    void XMA2FillFormatEx(XMA2WAVEFORMATEX *fmt, WORD blockCount, const struct WAVEBANKENTRY* entry) const;

    DWORD AdpcmSamplesPerBlock() const
    {
        DWORD nBlockAlign = (wBlockAlign + ADPCM_MINIWAVEFORMAT_BLOCKALIGN_CONVERSION_OFFSET) * nChannels;
        return nBlockAlign * 2 / (DWORD)nChannels - 12;
    }

    void AdpcmFillCoefficientTable(ADPCMWAVEFORMAT *fmt) const
    {
        // These are fixed since we are always using MS ADPCM
        fmt->wNumCoef = 7; /* MSADPCM_NUM_COEFFICIENTS */

        static ADPCMCOEFSET aCoef[7] = { { 256, 0}, {512, -256}, {0,0}, {192,64}, {240,0}, {460, -208}, {392,-232} };
        memcpy( &fmt->aCoef, aCoef, sizeof(aCoef) );
    }

#endif // __cplusplus

} WAVEBANKMINIWAVEFORMAT, *LPWAVEBANKMINIWAVEFORMAT;

typedef const WAVEBANKMINIWAVEFORMAT *LPCWAVEBANKMINIWAVEFORMAT;

//
// Entry meta-data
//

typedef struct WAVEBANKENTRY
{
    union
    {
        struct
        {
            // Entry flags
            DWORD                   dwFlags  :  4;

            // Duration of the wave, in units of one sample.
            // For instance, a ten second long wave sampled
            // at 48KHz would have a duration of 480,000.
            // This value is not affected by the number of
            // channels, the number of bits per sample, or the
            // compression format of the wave.
            DWORD                   Duration : 28;
        };
        DWORD dwFlagsAndDuration;
    };

    WAVEBANKMINIWAVEFORMAT  Format;         // Entry format.
    WAVEBANKREGION          PlayRegion;     // Region within the wave data segment that contains this entry.
    WAVEBANKSAMPLEREGION    LoopRegion;     // Region within the wave data (in samples) that should loop.

#ifdef __cplusplus

    void SwapBytes(void)
    {
        XACTWaveBank::SwapBytes(dwFlagsAndDuration);
        Format.SwapBytes();
        PlayRegion.SwapBytes();
        LoopRegion.SwapBytes();
    }

#endif // __cplusplus

} WAVEBANKENTRY, *LPWAVEBANKENTRY;

typedef const WAVEBANKENTRY *LPCWAVEBANKENTRY;

//
// Compact entry meta-data
//

typedef struct WAVEBANKENTRYCOMPACT
{
    DWORD       dwOffset            : 21;       // Data offset, in sectors
    DWORD       dwLengthDeviation   : 11;       // Data length deviation, in bytes

#ifdef __cplusplus

    void SwapBytes(void)
    {
        XACTWaveBank::SwapBytes(*(LPDWORD)this);
    }

#endif // __cplusplus

} WAVEBANKENTRYCOMPACT, *LPWAVEBANKENTRYCOMPACT;

typedef const WAVEBANKENTRYCOMPACT *LPCWAVEBANKENTRYCOMPACT;

//
// Bank data segment
//

typedef struct WAVEBANKDATA
{
    DWORD                   dwFlags;                                // Bank flags
    DWORD                   dwEntryCount;                           // Number of entries in the bank
    CHAR                    szBankName[WAVEBANK_BANKNAME_LENGTH];   // Bank friendly name
    DWORD                   dwEntryMetaDataElementSize;             // Size of each entry meta-data element, in bytes
    DWORD                   dwEntryNameElementSize;                 // Size of each entry name element, in bytes
    DWORD                   dwAlignment;                            // Entry alignment, in bytes
    WAVEBANKMINIWAVEFORMAT  CompactFormat;                          // Format data for compact bank
    FILETIME                BuildTime;                              // Build timestamp

#ifdef __cplusplus

    void SwapBytes(void)
    {
        XACTWaveBank::SwapBytes(dwFlags);
        XACTWaveBank::SwapBytes(dwEntryCount);
        XACTWaveBank::SwapBytes(dwEntryMetaDataElementSize);
        XACTWaveBank::SwapBytes(dwEntryNameElementSize);
        XACTWaveBank::SwapBytes(dwAlignment);
        CompactFormat.SwapBytes();
        XACTWaveBank::SwapBytes(BuildTime.dwLowDateTime);
        XACTWaveBank::SwapBytes(BuildTime.dwHighDateTime);
    }

#endif // __cplusplus

} WAVEBANKDATA, *LPWAVEBANKDATA;

typedef const WAVEBANKDATA *LPCWAVEBANKDATA;

inline void WAVEBANKMINIWAVEFORMAT::XMA2FillFormatEx(XMA2WAVEFORMATEX *fmt, WORD blockCount, const WAVEBANKENTRY* entry) const
{
    // Note caller is responsbile for filling out fmt->wfx with other helper functions.

    fmt->NumStreams = (WORD)( (nChannels + 1) / 2 );

    switch (nChannels)
    {
        case 1: fmt->ChannelMask =  SPEAKER_MONO; break;
        case 2: fmt->ChannelMask =  SPEAKER_STEREO; break;
        case 3: fmt->ChannelMask =  SPEAKER_2POINT1; break;
        case 4: fmt->ChannelMask =  SPEAKER_QUAD; break;
        case 5: fmt->ChannelMask =  SPEAKER_4POINT1; break;
        case 6: fmt->ChannelMask =  SPEAKER_5POINT1; break;
        case 7: fmt->ChannelMask =  SPEAKER_5POINT1 | SPEAKER_BACK_CENTER; break;
        case 8: fmt->ChannelMask =  SPEAKER_7POINT1; break;
        default: fmt->ChannelMask = 0; break;
    }

    fmt->SamplesEncoded = entry->Duration;
    fmt->BytesPerBlock = 65536; /* XACT_FIXED_XMA_BLOCK_SIZE */

    fmt->PlayBegin = entry->PlayRegion.dwOffset;
    fmt->PlayLength = entry->PlayRegion.dwLength;

    if (entry->LoopRegion.dwTotalSamples > 0)
    {
        fmt->LoopBegin = entry->LoopRegion.dwStartSample;
        fmt->LoopLength = entry->LoopRegion.dwTotalSamples;
        fmt->LoopCount = 0xff; /* XACTLOOPCOUNT_INFINITE */
    }
    else
    {
        fmt->LoopBegin = 0;
        fmt->LoopLength = 0;
        fmt->LoopCount = 0;
    }

    fmt->EncoderVersion = 4; // XMAENCODER_VERSION_XMA2

    fmt->BlockCount = blockCount;
}

#ifdef _M_PPCBE
#pragma bitfield_order(pop)
#endif

#pragma warning(pop)
#pragma pack(pop)

#endif // __XACTWB_H__