mirror of
https://github.com/ncblakely/GiantsTools
synced 2024-12-23 15:57:22 +01:00
1387 lines
43 KiB
C++
1387 lines
43 KiB
C++
|
//--------------------------------------------------------------------------------------
|
||
|
// File: WaveBankReader.cpp
|
||
|
//
|
||
|
// Functions for loading audio data from Wave Banks
|
||
|
//
|
||
|
// 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 "WaveBankReader.h"
|
||
|
#include "Audio.h"
|
||
|
#include "PlatformHelpers.h"
|
||
|
#include "SoundCommon.h"
|
||
|
|
||
|
#if (defined(_XBOX_ONE) && defined(_TITLE)) || defined(_GAMING_XBOX)
|
||
|
#include <apu.h>
|
||
|
#include <shapexmacontext.h>
|
||
|
#endif
|
||
|
|
||
|
|
||
|
namespace
|
||
|
{
|
||
|
#pragma pack(push, 1)
|
||
|
|
||
|
constexpr size_t DVD_SECTOR_SIZE = 2048;
|
||
|
constexpr size_t DVD_BLOCK_SIZE = DVD_SECTOR_SIZE * 16;
|
||
|
|
||
|
constexpr size_t ALIGNMENT_MIN = 4;
|
||
|
constexpr size_t ALIGNMENT_DVD = DVD_SECTOR_SIZE;
|
||
|
|
||
|
constexpr size_t MAX_DATA_SEGMENT_SIZE = 0xFFFFFFFF;
|
||
|
constexpr size_t MAX_COMPACT_DATA_SEGMENT_SIZE = 0x001FFFFF;
|
||
|
|
||
|
struct REGION
|
||
|
{
|
||
|
uint32_t dwOffset; // Region offset, in bytes.
|
||
|
uint32_t dwLength; // Region length, in bytes.
|
||
|
|
||
|
void BigEndian() noexcept
|
||
|
{
|
||
|
dwOffset = _byteswap_ulong(dwOffset);
|
||
|
dwLength = _byteswap_ulong(dwLength);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
struct SAMPLEREGION
|
||
|
{
|
||
|
uint32_t dwStartSample; // Start sample for the region.
|
||
|
uint32_t dwTotalSamples; // Region length in samples.
|
||
|
|
||
|
void BigEndian() noexcept
|
||
|
{
|
||
|
dwStartSample = _byteswap_ulong(dwStartSample);
|
||
|
dwTotalSamples = _byteswap_ulong(dwTotalSamples);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
struct HEADER
|
||
|
{
|
||
|
static const uint32_t SIGNATURE = MAKEFOURCC('W', 'B', 'N', 'D');
|
||
|
static const uint32_t BE_SIGNATURE = MAKEFOURCC('D', 'N', 'B', 'W');
|
||
|
static const uint32_t VERSION = 44;
|
||
|
|
||
|
enum SEGIDX
|
||
|
{
|
||
|
SEGIDX_BANKDATA = 0, // Bank data
|
||
|
SEGIDX_ENTRYMETADATA, // Entry meta-data
|
||
|
SEGIDX_SEEKTABLES, // Storage for seek tables for the encoded waves.
|
||
|
SEGIDX_ENTRYNAMES, // Entry friendly names
|
||
|
SEGIDX_ENTRYWAVEDATA, // Entry wave data
|
||
|
SEGIDX_COUNT
|
||
|
};
|
||
|
|
||
|
uint32_t dwSignature; // File signature
|
||
|
uint32_t dwVersion; // Version of the tool that created the file
|
||
|
uint32_t dwHeaderVersion; // Version of the file format
|
||
|
REGION Segments[SEGIDX_COUNT]; // Segment lookup table
|
||
|
|
||
|
void BigEndian() noexcept
|
||
|
{
|
||
|
// Leave dwSignature alone as indicator of BE vs. LE
|
||
|
|
||
|
dwVersion = _byteswap_ulong(dwVersion);
|
||
|
dwHeaderVersion = _byteswap_ulong(dwHeaderVersion);
|
||
|
for (size_t j = 0; j < SEGIDX_COUNT; ++j)
|
||
|
{
|
||
|
Segments[j].BigEndian();
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
#pragma warning( disable : 4201 4203 )
|
||
|
|
||
|
union MINIWAVEFORMAT
|
||
|
{
|
||
|
static const uint32_t TAG_PCM = 0x0;
|
||
|
static const uint32_t TAG_XMA = 0x1;
|
||
|
static const uint32_t TAG_ADPCM = 0x2;
|
||
|
static const uint32_t TAG_WMA = 0x3;
|
||
|
|
||
|
static const uint32_t BITDEPTH_8 = 0x0; // PCM only
|
||
|
static const uint32_t BITDEPTH_16 = 0x1; // PCM only
|
||
|
|
||
|
static const size_t ADPCM_BLOCKALIGN_CONVERSION_OFFSET = 22;
|
||
|
|
||
|
struct
|
||
|
{
|
||
|
uint32_t wFormatTag : 2; // Format tag
|
||
|
uint32_t nChannels : 3; // Channel count (1 - 6)
|
||
|
uint32_t nSamplesPerSec : 18; // Sampling rate
|
||
|
uint32_t wBlockAlign : 8; // Block alignment. For WMA, lower 6 bits block alignment index, upper 2 bits bytes-per-second index.
|
||
|
uint32_t wBitsPerSample : 1; // Bits per sample (8 vs. 16, PCM only); WMAudio2/WMAudio3 (for WMA)
|
||
|
};
|
||
|
|
||
|
uint32_t dwValue;
|
||
|
|
||
|
void BigEndian() noexcept
|
||
|
{
|
||
|
dwValue = _byteswap_ulong(dwValue);
|
||
|
}
|
||
|
|
||
|
WORD BitsPerSample() const noexcept
|
||
|
{
|
||
|
if (wFormatTag == TAG_XMA)
|
||
|
return 16; // XMA_OUTPUT_SAMPLE_BITS == 16
|
||
|
if (wFormatTag == TAG_WMA)
|
||
|
return 16;
|
||
|
if (wFormatTag == TAG_ADPCM)
|
||
|
return 4; // MSADPCM_BITS_PER_SAMPLE == 4
|
||
|
|
||
|
// wFormatTag must be TAG_PCM (2 bits can only represent 4 different values)
|
||
|
return (wBitsPerSample == BITDEPTH_16) ? 16u : 8u;
|
||
|
}
|
||
|
|
||
|
DWORD BlockAlign() const noexcept
|
||
|
{
|
||
|
switch (wFormatTag)
|
||
|
{
|
||
|
case TAG_PCM:
|
||
|
return wBlockAlign;
|
||
|
|
||
|
case TAG_XMA:
|
||
|
return (nChannels * 16 / 8); // XMA_OUTPUT_SAMPLE_BITS = 16
|
||
|
|
||
|
case TAG_ADPCM:
|
||
|
return (wBlockAlign + ADPCM_BLOCKALIGN_CONVERSION_OFFSET) * nChannels;
|
||
|
|
||
|
case TAG_WMA:
|
||
|
{
|
||
|
static const uint32_t aWMABlockAlign[] =
|
||
|
{
|
||
|
929,
|
||
|
1487,
|
||
|
1280,
|
||
|
2230,
|
||
|
8917,
|
||
|
8192,
|
||
|
4459,
|
||
|
5945,
|
||
|
2304,
|
||
|
1536,
|
||
|
1485,
|
||
|
1008,
|
||
|
2731,
|
||
|
4096,
|
||
|
6827,
|
||
|
5462,
|
||
|
1280
|
||
|
};
|
||
|
|
||
|
uint32_t dwBlockAlignIndex = wBlockAlign & 0x1F;
|
||
|
if (dwBlockAlignIndex < _countof(aWMABlockAlign))
|
||
|
return aWMABlockAlign[dwBlockAlignIndex];
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
DWORD AvgBytesPerSec() const noexcept
|
||
|
{
|
||
|
switch (wFormatTag)
|
||
|
{
|
||
|
case TAG_PCM:
|
||
|
return nSamplesPerSec * wBlockAlign;
|
||
|
|
||
|
case TAG_XMA:
|
||
|
return nSamplesPerSec * BlockAlign();
|
||
|
|
||
|
case TAG_ADPCM:
|
||
|
{
|
||
|
uint32_t blockAlign = BlockAlign();
|
||
|
uint32_t samplesPerAdpcmBlock = AdpcmSamplesPerBlock();
|
||
|
return blockAlign * nSamplesPerSec / samplesPerAdpcmBlock;
|
||
|
}
|
||
|
|
||
|
case TAG_WMA:
|
||
|
{
|
||
|
static const uint32_t aWMAAvgBytesPerSec[] =
|
||
|
{
|
||
|
12000,
|
||
|
24000,
|
||
|
4000,
|
||
|
6000,
|
||
|
8000,
|
||
|
20000,
|
||
|
2500
|
||
|
};
|
||
|
// bitrate = entry * 8
|
||
|
|
||
|
uint32_t dwBytesPerSecIndex = wBlockAlign >> 5;
|
||
|
if (dwBytesPerSecIndex < _countof(aWMAAvgBytesPerSec))
|
||
|
return aWMAAvgBytesPerSec[dwBytesPerSecIndex];
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
DWORD AdpcmSamplesPerBlock() const noexcept
|
||
|
{
|
||
|
uint32_t nBlockAlign = (wBlockAlign + ADPCM_BLOCKALIGN_CONVERSION_OFFSET) * nChannels;
|
||
|
return nBlockAlign * 2 / uint32_t(nChannels) - 12;
|
||
|
}
|
||
|
|
||
|
void AdpcmFillCoefficientTable(ADPCMWAVEFORMAT *fmt) const noexcept
|
||
|
{
|
||
|
// 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));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
struct BANKDATA
|
||
|
{
|
||
|
static const size_t BANKNAME_LENGTH = 64;
|
||
|
|
||
|
static const uint32_t TYPE_BUFFER = 0x00000000;
|
||
|
static const uint32_t TYPE_STREAMING = 0x00000001;
|
||
|
static const uint32_t TYPE_MASK = 0x00000001;
|
||
|
|
||
|
static const uint32_t FLAGS_ENTRYNAMES = 0x00010000;
|
||
|
static const uint32_t FLAGS_COMPACT = 0x00020000;
|
||
|
static const uint32_t FLAGS_SYNC_DISABLED = 0x00040000;
|
||
|
static const uint32_t FLAGS_SEEKTABLES = 0x00080000;
|
||
|
static const uint32_t FLAGS_MASK = 0x000F0000;
|
||
|
|
||
|
uint32_t dwFlags; // Bank flags
|
||
|
uint32_t dwEntryCount; // Number of entries in the bank
|
||
|
char szBankName[BANKNAME_LENGTH]; // Bank friendly name
|
||
|
uint32_t dwEntryMetaDataElementSize; // Size of each entry meta-data element, in bytes
|
||
|
uint32_t dwEntryNameElementSize; // Size of each entry name element, in bytes
|
||
|
uint32_t dwAlignment; // Entry alignment, in bytes
|
||
|
MINIWAVEFORMAT CompactFormat; // Format data for compact bank
|
||
|
FILETIME BuildTime; // Build timestamp
|
||
|
|
||
|
void BigEndian() noexcept
|
||
|
{
|
||
|
dwFlags = _byteswap_ulong(dwFlags);
|
||
|
dwEntryCount = _byteswap_ulong(dwEntryCount);
|
||
|
dwEntryMetaDataElementSize = _byteswap_ulong(dwEntryMetaDataElementSize);
|
||
|
dwEntryNameElementSize = _byteswap_ulong(dwEntryNameElementSize);
|
||
|
dwAlignment = _byteswap_ulong(dwAlignment);
|
||
|
CompactFormat.BigEndian();
|
||
|
BuildTime.dwLowDateTime = _byteswap_ulong(BuildTime.dwLowDateTime);
|
||
|
BuildTime.dwHighDateTime = _byteswap_ulong(BuildTime.dwHighDateTime);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
struct ENTRY
|
||
|
{
|
||
|
static const uint32_t FLAGS_READAHEAD = 0x00000001; // Enable stream read-ahead
|
||
|
static const uint32_t FLAGS_LOOPCACHE = 0x00000002; // One or more looping sounds use this wave
|
||
|
static const uint32_t FLAGS_REMOVELOOPTAIL = 0x00000004;// Remove data after the end of the loop region
|
||
|
static const uint32_t FLAGS_IGNORELOOP = 0x00000008; // Used internally when the loop region can't be used
|
||
|
static const uint32_t FLAGS_MASK = 0x00000008;
|
||
|
|
||
|
union
|
||
|
{
|
||
|
struct
|
||
|
{
|
||
|
// Entry flags
|
||
|
uint32_t 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.
|
||
|
uint32_t Duration : 28;
|
||
|
};
|
||
|
uint32_t dwFlagsAndDuration;
|
||
|
};
|
||
|
|
||
|
MINIWAVEFORMAT Format; // Entry format.
|
||
|
REGION PlayRegion; // Region within the wave data segment that contains this entry.
|
||
|
SAMPLEREGION LoopRegion; // Region within the wave data (in samples) that should loop.
|
||
|
|
||
|
void BigEndian() noexcept
|
||
|
{
|
||
|
dwFlagsAndDuration = _byteswap_ulong(dwFlagsAndDuration);
|
||
|
Format.BigEndian();
|
||
|
PlayRegion.BigEndian();
|
||
|
LoopRegion.BigEndian();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
struct ENTRYCOMPACT
|
||
|
{
|
||
|
uint32_t dwOffset : 21; // Data offset, in multiplies of the bank alignment
|
||
|
uint32_t dwLengthDeviation : 11; // Data length deviation, in bytes
|
||
|
|
||
|
void BigEndian() noexcept
|
||
|
{
|
||
|
*reinterpret_cast<uint32_t*>(this) = _byteswap_ulong(*reinterpret_cast<const uint32_t*>(this));
|
||
|
}
|
||
|
|
||
|
void ComputeLocations(DWORD& offset, DWORD& length, uint32_t index, const HEADER& header, const BANKDATA& data, const ENTRYCOMPACT* entries) const noexcept
|
||
|
{
|
||
|
offset = dwOffset * data.dwAlignment;
|
||
|
|
||
|
if (index < (data.dwEntryCount - 1))
|
||
|
{
|
||
|
length = (entries[index + 1].dwOffset * data.dwAlignment) - offset - dwLengthDeviation;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
length = header.Segments[HEADER::SEGIDX_ENTRYWAVEDATA].dwLength - offset - dwLengthDeviation;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static uint32_t GetDuration(DWORD length, const BANKDATA& data, const uint32_t* seekTable) noexcept
|
||
|
{
|
||
|
switch (data.CompactFormat.wFormatTag)
|
||
|
{
|
||
|
case MINIWAVEFORMAT::TAG_ADPCM:
|
||
|
{
|
||
|
uint32_t duration = (length / data.CompactFormat.BlockAlign()) * data.CompactFormat.AdpcmSamplesPerBlock();
|
||
|
uint32_t partial = length % data.CompactFormat.BlockAlign();
|
||
|
if (partial)
|
||
|
{
|
||
|
if (partial >= (7u * data.CompactFormat.nChannels))
|
||
|
duration += (partial * 2 / data.CompactFormat.nChannels - 12);
|
||
|
}
|
||
|
return duration;
|
||
|
}
|
||
|
|
||
|
case MINIWAVEFORMAT::TAG_WMA:
|
||
|
if (seekTable)
|
||
|
{
|
||
|
uint32_t seekCount = *seekTable;
|
||
|
if (seekCount > 0)
|
||
|
{
|
||
|
return seekTable[seekCount] / uint32_t(2 * data.CompactFormat.nChannels);
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
|
||
|
case MINIWAVEFORMAT::TAG_XMA:
|
||
|
if (seekTable)
|
||
|
{
|
||
|
uint32_t seekCount = *seekTable;
|
||
|
if (seekCount > 0)
|
||
|
{
|
||
|
return seekTable[seekCount];
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
|
||
|
default:
|
||
|
return uint32_t((uint64_t(length) * 8)
|
||
|
/ (uint64_t(data.CompactFormat.BitsPerSample()) * uint64_t(data.CompactFormat.nChannels)));
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
#pragma pack(pop)
|
||
|
|
||
|
inline const uint32_t* FindSeekTable(uint32_t index, const uint8_t* seekTable, const HEADER& header, const BANKDATA& data) noexcept
|
||
|
{
|
||
|
if (!seekTable || index >= data.dwEntryCount)
|
||
|
return nullptr;
|
||
|
|
||
|
uint32_t seekSize = header.Segments[HEADER::SEGIDX_SEEKTABLES].dwLength;
|
||
|
|
||
|
if ((index * sizeof(uint32_t)) > seekSize)
|
||
|
return nullptr;
|
||
|
|
||
|
auto table = reinterpret_cast<const uint32_t*>(seekTable);
|
||
|
uint32_t offset = table[index];
|
||
|
if (offset == uint32_t(-1))
|
||
|
return nullptr;
|
||
|
|
||
|
offset += sizeof(uint32_t) * data.dwEntryCount;
|
||
|
|
||
|
if (offset > seekSize)
|
||
|
return nullptr;
|
||
|
|
||
|
return reinterpret_cast<const uint32_t*>(seekTable + offset);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static_assert(sizeof(REGION) == 8, "Mismatch with xact3wb.h");
|
||
|
static_assert(sizeof(SAMPLEREGION) == 8, "Mismatch with xact3wb.h");
|
||
|
static_assert(sizeof(HEADER) == 52, "Mismatch with xact3wb.h");
|
||
|
static_assert(sizeof(ENTRY) == 24, "Mismatch with xact3wb.h");
|
||
|
static_assert(sizeof(MINIWAVEFORMAT) == 4, "Mismatch with xact3wb.h");
|
||
|
static_assert(sizeof(ENTRY) == 24, "Mismatch with xact3wb.h");
|
||
|
static_assert(sizeof(ENTRYCOMPACT) == 4, "Mismatch with xact3wb.h");
|
||
|
static_assert(sizeof(BANKDATA) == 96, "Mismatch with xact3wb.h");
|
||
|
|
||
|
using namespace DirectX;
|
||
|
|
||
|
//--------------------------------------------------------------------------------------
|
||
|
class WaveBankReader::Impl
|
||
|
{
|
||
|
public:
|
||
|
Impl() noexcept :
|
||
|
m_async(INVALID_HANDLE_VALUE),
|
||
|
m_request{},
|
||
|
m_prepared(false),
|
||
|
m_header{},
|
||
|
m_data{}
|
||
|
#ifdef DIRECTX_ENABLE_XMA2
|
||
|
, m_xmaMemory(nullptr)
|
||
|
#endif
|
||
|
{
|
||
|
}
|
||
|
|
||
|
Impl(Impl&&) = default;
|
||
|
Impl& operator= (Impl&&) = default;
|
||
|
|
||
|
Impl(Impl const&) = delete;
|
||
|
Impl& operator= (Impl const&) = delete;
|
||
|
|
||
|
~Impl() { Close(); }
|
||
|
|
||
|
HRESULT Open(_In_z_ const wchar_t* szFileName) noexcept(false);
|
||
|
void Close() noexcept;
|
||
|
|
||
|
HRESULT GetFormat(_In_ uint32_t index, _Out_writes_bytes_(maxsize) WAVEFORMATEX* pFormat, _In_ size_t maxsize) const noexcept;
|
||
|
|
||
|
HRESULT GetWaveData(_In_ uint32_t index, _Outptr_ const uint8_t** pData, _Out_ uint32_t& dataSize) const noexcept;
|
||
|
|
||
|
HRESULT GetSeekTable(_In_ uint32_t index, _Out_ const uint32_t** pData, _Out_ uint32_t& dataCount, _Out_ uint32_t& tag) const noexcept;
|
||
|
|
||
|
HRESULT GetMetadata(_In_ uint32_t index, _Out_ Metadata& metadata) const noexcept;
|
||
|
|
||
|
bool UpdatePrepared() noexcept;
|
||
|
|
||
|
void Clear() noexcept
|
||
|
{
|
||
|
memset(&m_header, 0, sizeof(HEADER));
|
||
|
memset(&m_data, 0, sizeof(BANKDATA));
|
||
|
|
||
|
m_names.clear();
|
||
|
m_entries.reset();
|
||
|
m_seekData.reset();
|
||
|
m_waveData.reset();
|
||
|
|
||
|
#ifdef DIRECTX_ENABLE_XMA2
|
||
|
if (m_xmaMemory)
|
||
|
{
|
||
|
ApuFree(m_xmaMemory);
|
||
|
m_xmaMemory = nullptr;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
HANDLE m_async;
|
||
|
ScopedHandle m_event;
|
||
|
OVERLAPPED m_request;
|
||
|
bool m_prepared;
|
||
|
|
||
|
HEADER m_header;
|
||
|
BANKDATA m_data;
|
||
|
std::map<std::string, uint32_t> m_names;
|
||
|
|
||
|
private:
|
||
|
std::unique_ptr<uint8_t[]> m_entries;
|
||
|
std::unique_ptr<uint8_t[]> m_seekData;
|
||
|
std::unique_ptr<uint8_t[]> m_waveData;
|
||
|
|
||
|
#ifdef DIRECTX_ENABLE_XMA2
|
||
|
public:
|
||
|
void* m_xmaMemory;
|
||
|
#endif
|
||
|
};
|
||
|
|
||
|
|
||
|
_Use_decl_annotations_
|
||
|
HRESULT WaveBankReader::Impl::Open(const wchar_t* szFileName) noexcept(false)
|
||
|
{
|
||
|
Close();
|
||
|
Clear();
|
||
|
|
||
|
m_prepared = false;
|
||
|
|
||
|
m_event.reset(CreateEventEx(nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, EVENT_MODIFY_STATE | SYNCHRONIZE));
|
||
|
if (!m_event)
|
||
|
{
|
||
|
return HRESULT_FROM_WIN32(GetLastError());
|
||
|
}
|
||
|
|
||
|
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
|
||
|
CREATEFILE2_EXTENDED_PARAMETERS params = { sizeof(CREATEFILE2_EXTENDED_PARAMETERS), 0, 0, 0, {}, nullptr };
|
||
|
params.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
|
||
|
params.dwFileFlags = FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN;
|
||
|
ScopedHandle hFile(safe_handle(CreateFile2(szFileName,
|
||
|
GENERIC_READ,
|
||
|
FILE_SHARE_READ,
|
||
|
OPEN_EXISTING,
|
||
|
¶ms)));
|
||
|
#else
|
||
|
ScopedHandle hFile(safe_handle(CreateFileW(szFileName,
|
||
|
GENERIC_READ,
|
||
|
FILE_SHARE_READ,
|
||
|
nullptr,
|
||
|
OPEN_EXISTING,
|
||
|
FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN,
|
||
|
nullptr)));
|
||
|
#endif
|
||
|
|
||
|
if (!hFile)
|
||
|
{
|
||
|
return HRESULT_FROM_WIN32(GetLastError());
|
||
|
}
|
||
|
|
||
|
// Read and verify header
|
||
|
OVERLAPPED request = {};
|
||
|
request.hEvent = m_event.get();
|
||
|
|
||
|
bool wait = false;
|
||
|
if (!ReadFile(hFile.get(), &m_header, sizeof(m_header), nullptr, &request))
|
||
|
{
|
||
|
DWORD error = GetLastError();
|
||
|
if (error != ERROR_IO_PENDING)
|
||
|
return HRESULT_FROM_WIN32(error);
|
||
|
wait = true;
|
||
|
}
|
||
|
|
||
|
DWORD bytes;
|
||
|
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
|
||
|
BOOL result = GetOverlappedResultEx(hFile.get(), &request, &bytes, INFINITE, FALSE);
|
||
|
#else
|
||
|
if (wait)
|
||
|
(void)WaitForSingleObject(m_event.get(), INFINITE);
|
||
|
|
||
|
BOOL result = GetOverlappedResult(hFile.get(), &request, &bytes, FALSE);
|
||
|
#endif
|
||
|
|
||
|
if (!result || (bytes != sizeof(m_header)))
|
||
|
{
|
||
|
return HRESULT_FROM_WIN32(GetLastError());
|
||
|
}
|
||
|
|
||
|
if (m_header.dwSignature != HEADER::SIGNATURE && m_header.dwSignature != HEADER::BE_SIGNATURE)
|
||
|
{
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
bool be = (m_header.dwSignature == HEADER::BE_SIGNATURE);
|
||
|
if (be)
|
||
|
{
|
||
|
DebugTrace("INFO: \"%ls\" is a big-endian (Xbox 360) wave bank\n", szFileName);
|
||
|
m_header.BigEndian();
|
||
|
}
|
||
|
|
||
|
if (m_header.dwHeaderVersion != HEADER::VERSION)
|
||
|
{
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
// Load bank data
|
||
|
memset(&request, 0, sizeof(request));
|
||
|
request.Offset = m_header.Segments[HEADER::SEGIDX_BANKDATA].dwOffset;
|
||
|
request.hEvent = m_event.get();
|
||
|
|
||
|
wait = false;
|
||
|
if (!ReadFile(hFile.get(), &m_data, sizeof(m_data), nullptr, &request))
|
||
|
{
|
||
|
DWORD error = GetLastError();
|
||
|
if (error != ERROR_IO_PENDING)
|
||
|
return HRESULT_FROM_WIN32(error);
|
||
|
wait = true;
|
||
|
}
|
||
|
|
||
|
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
|
||
|
result = GetOverlappedResultEx(hFile.get(), &request, &bytes, INFINITE, FALSE);
|
||
|
#else
|
||
|
if (wait)
|
||
|
(void)WaitForSingleObject(m_event.get(), INFINITE);
|
||
|
|
||
|
result = GetOverlappedResult(hFile.get(), &request, &bytes, FALSE);
|
||
|
#endif
|
||
|
|
||
|
if (!result || (bytes != sizeof(m_data)))
|
||
|
{
|
||
|
return HRESULT_FROM_WIN32(GetLastError());
|
||
|
}
|
||
|
|
||
|
if (be)
|
||
|
m_data.BigEndian();
|
||
|
|
||
|
if (!m_data.dwEntryCount)
|
||
|
{
|
||
|
return HRESULT_FROM_WIN32(ERROR_NO_DATA);
|
||
|
}
|
||
|
|
||
|
if (m_data.dwFlags & BANKDATA::TYPE_STREAMING)
|
||
|
{
|
||
|
if (m_data.dwAlignment < ALIGNMENT_DVD)
|
||
|
return E_FAIL;
|
||
|
if (m_data.dwAlignment % DVD_SECTOR_SIZE)
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
else if (m_data.dwAlignment < ALIGNMENT_MIN)
|
||
|
{
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
if (m_data.dwFlags & BANKDATA::FLAGS_COMPACT)
|
||
|
{
|
||
|
if (m_data.dwEntryMetaDataElementSize != sizeof(ENTRYCOMPACT))
|
||
|
{
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
if (m_header.Segments[HEADER::SEGIDX_ENTRYWAVEDATA].dwLength > (MAX_COMPACT_DATA_SEGMENT_SIZE * m_data.dwAlignment))
|
||
|
{
|
||
|
// Data segment is too large to be valid compact wavebank
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (m_data.dwEntryMetaDataElementSize != sizeof(ENTRY))
|
||
|
{
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DWORD metadataBytes = m_header.Segments[HEADER::SEGIDX_ENTRYMETADATA].dwLength;
|
||
|
if (metadataBytes != (m_data.dwEntryCount * m_data.dwEntryMetaDataElementSize))
|
||
|
{
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
// Load names
|
||
|
DWORD namesBytes = m_header.Segments[HEADER::SEGIDX_ENTRYNAMES].dwLength;
|
||
|
if (namesBytes > 0)
|
||
|
{
|
||
|
if (namesBytes >= (m_data.dwEntryNameElementSize * m_data.dwEntryCount))
|
||
|
{
|
||
|
std::unique_ptr<char[]> temp(new (std::nothrow) char[namesBytes]);
|
||
|
if (!temp)
|
||
|
return E_OUTOFMEMORY;
|
||
|
|
||
|
memset(&request, 0, sizeof(request));
|
||
|
request.Offset = m_header.Segments[HEADER::SEGIDX_ENTRYNAMES].dwOffset;
|
||
|
request.hEvent = m_event.get();
|
||
|
|
||
|
wait = false;
|
||
|
if (!ReadFile(hFile.get(), temp.get(), namesBytes, nullptr, &request))
|
||
|
{
|
||
|
DWORD error = GetLastError();
|
||
|
if (error != ERROR_IO_PENDING)
|
||
|
return HRESULT_FROM_WIN32(error);
|
||
|
wait = true;
|
||
|
}
|
||
|
|
||
|
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
|
||
|
result = GetOverlappedResultEx(hFile.get(), &request, &bytes, INFINITE, FALSE);
|
||
|
#else
|
||
|
if (wait)
|
||
|
(void)WaitForSingleObject(m_event.get(), INFINITE);
|
||
|
|
||
|
result = GetOverlappedResult(hFile.get(), &request, &bytes, FALSE);
|
||
|
#endif
|
||
|
|
||
|
if (!result || (namesBytes != bytes))
|
||
|
{
|
||
|
return HRESULT_FROM_WIN32(GetLastError());
|
||
|
}
|
||
|
|
||
|
for (uint32_t j = 0; j < m_data.dwEntryCount; ++j)
|
||
|
{
|
||
|
DWORD n = m_data.dwEntryNameElementSize * j;
|
||
|
|
||
|
char name[64] = {};
|
||
|
strncpy_s(name, &temp[n], sizeof(name));
|
||
|
|
||
|
m_names[name] = j;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Load entries
|
||
|
if (m_data.dwFlags & BANKDATA::FLAGS_COMPACT)
|
||
|
{
|
||
|
m_entries.reset(reinterpret_cast<uint8_t*>(new (std::nothrow) ENTRYCOMPACT[m_data.dwEntryCount]));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_entries.reset(reinterpret_cast<uint8_t*>(new (std::nothrow) ENTRY[m_data.dwEntryCount]));
|
||
|
}
|
||
|
if (!m_entries)
|
||
|
return E_OUTOFMEMORY;
|
||
|
|
||
|
memset(&request, 0, sizeof(request));
|
||
|
request.Offset = m_header.Segments[HEADER::SEGIDX_ENTRYMETADATA].dwOffset;
|
||
|
request.hEvent = m_event.get();
|
||
|
|
||
|
wait = false;
|
||
|
if (!ReadFile(hFile.get(), m_entries.get(), metadataBytes, nullptr, &request))
|
||
|
{
|
||
|
DWORD error = GetLastError();
|
||
|
if (error != ERROR_IO_PENDING)
|
||
|
return HRESULT_FROM_WIN32(error);
|
||
|
wait = true;
|
||
|
}
|
||
|
|
||
|
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
|
||
|
result = GetOverlappedResultEx(hFile.get(), &request, &bytes, INFINITE, FALSE);
|
||
|
#else
|
||
|
if (wait)
|
||
|
(void)WaitForSingleObject(m_event.get(), INFINITE);
|
||
|
|
||
|
result = GetOverlappedResult(hFile.get(), &request, &bytes, FALSE);
|
||
|
#endif
|
||
|
|
||
|
if (!result || (metadataBytes != bytes))
|
||
|
{
|
||
|
return HRESULT_FROM_WIN32(GetLastError());
|
||
|
}
|
||
|
|
||
|
if (be)
|
||
|
{
|
||
|
if (m_data.dwFlags & BANKDATA::FLAGS_COMPACT)
|
||
|
{
|
||
|
auto ptr = reinterpret_cast<ENTRYCOMPACT*>(m_entries.get());
|
||
|
for (size_t j = 0; j < m_data.dwEntryCount; ++j, ++ptr)
|
||
|
ptr->BigEndian();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
auto ptr = reinterpret_cast<ENTRY*>(m_entries.get());
|
||
|
for (size_t j = 0; j < m_data.dwEntryCount; ++j, ++ptr)
|
||
|
ptr->BigEndian();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Load seek tables (XMA2 / xWMA)
|
||
|
DWORD seekLen = m_header.Segments[HEADER::SEGIDX_SEEKTABLES].dwLength;
|
||
|
if (seekLen > 0)
|
||
|
{
|
||
|
m_seekData.reset(new (std::nothrow) uint8_t[seekLen]);
|
||
|
if (!m_seekData)
|
||
|
return E_OUTOFMEMORY;
|
||
|
|
||
|
memset(&request, 0, sizeof(OVERLAPPED));
|
||
|
request.Offset = m_header.Segments[HEADER::SEGIDX_SEEKTABLES].dwOffset;
|
||
|
request.hEvent = m_event.get();
|
||
|
|
||
|
wait = false;
|
||
|
if (!ReadFile(hFile.get(), m_seekData.get(), seekLen, nullptr, &request))
|
||
|
{
|
||
|
DWORD error = GetLastError();
|
||
|
if (error != ERROR_IO_PENDING)
|
||
|
return HRESULT_FROM_WIN32(error);
|
||
|
wait = true;
|
||
|
}
|
||
|
|
||
|
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
|
||
|
result = GetOverlappedResultEx(hFile.get(), &request, &bytes, INFINITE, FALSE);
|
||
|
#else
|
||
|
if (wait)
|
||
|
(void)WaitForSingleObject(m_event.get(), INFINITE);
|
||
|
|
||
|
result = GetOverlappedResult(hFile.get(), &request, &bytes, FALSE);
|
||
|
#endif
|
||
|
|
||
|
if (!result || (seekLen != bytes))
|
||
|
{
|
||
|
return HRESULT_FROM_WIN32(GetLastError());
|
||
|
}
|
||
|
|
||
|
if (be)
|
||
|
{
|
||
|
auto ptr = reinterpret_cast<uint32_t*>(m_seekData.get());
|
||
|
for (size_t j = 0; j < seekLen; j += 4, ++ptr)
|
||
|
{
|
||
|
*ptr = _byteswap_ulong(*ptr);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DWORD waveLen = m_header.Segments[HEADER::SEGIDX_ENTRYWAVEDATA].dwLength;
|
||
|
if (!waveLen)
|
||
|
{
|
||
|
return HRESULT_FROM_WIN32(ERROR_NO_DATA);
|
||
|
}
|
||
|
|
||
|
if (m_data.dwFlags & BANKDATA::TYPE_STREAMING)
|
||
|
{
|
||
|
// If streaming, reopen without buffering
|
||
|
hFile.reset();
|
||
|
|
||
|
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
|
||
|
CREATEFILE2_EXTENDED_PARAMETERS params2 = { sizeof(CREATEFILE2_EXTENDED_PARAMETERS), 0, 0, 0, {}, nullptr };
|
||
|
params2.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
|
||
|
params2.dwFileFlags = FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING;
|
||
|
m_async = CreateFile2(szFileName,
|
||
|
GENERIC_READ,
|
||
|
FILE_SHARE_READ,
|
||
|
OPEN_EXISTING,
|
||
|
¶ms2);
|
||
|
#else
|
||
|
m_async = CreateFileW(szFileName,
|
||
|
GENERIC_READ,
|
||
|
FILE_SHARE_READ,
|
||
|
nullptr,
|
||
|
OPEN_EXISTING,
|
||
|
FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING,
|
||
|
nullptr);
|
||
|
#endif
|
||
|
|
||
|
if (m_async == INVALID_HANDLE_VALUE)
|
||
|
{
|
||
|
return HRESULT_FROM_WIN32(GetLastError());
|
||
|
}
|
||
|
|
||
|
m_prepared = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// If in-memory, kick off read of wave data
|
||
|
void* dest = nullptr;
|
||
|
|
||
|
#ifdef DIRECTX_ENABLE_XMA2
|
||
|
bool xma = false;
|
||
|
if (m_data.dwFlags & BANKDATA::FLAGS_COMPACT)
|
||
|
{
|
||
|
if (m_data.CompactFormat.wFormatTag == MINIWAVEFORMAT::TAG_XMA)
|
||
|
xma = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
for (uint32_t j = 0; j < m_data.dwEntryCount; ++j)
|
||
|
{
|
||
|
auto& entry = reinterpret_cast<const ENTRY*>(m_entries.get())[j];
|
||
|
if (entry.Format.wFormatTag == MINIWAVEFORMAT::TAG_XMA)
|
||
|
{
|
||
|
xma = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (xma)
|
||
|
{
|
||
|
HRESULT hr = ApuAlloc(&m_xmaMemory, nullptr, waveLen, SHAPE_XMA_INPUT_BUFFER_ALIGNMENT);
|
||
|
if (FAILED(hr))
|
||
|
{
|
||
|
DebugTrace("ERROR: ApuAlloc failed. Did you allocate a large enough heap with ApuCreateHeap for all your XMA wave data?\n");
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
dest = m_xmaMemory;
|
||
|
}
|
||
|
else
|
||
|
#endif // XMA2
|
||
|
{
|
||
|
m_waveData.reset(new (std::nothrow) uint8_t[waveLen]);
|
||
|
if (!m_waveData)
|
||
|
return E_OUTOFMEMORY;
|
||
|
|
||
|
dest = m_waveData.get();
|
||
|
}
|
||
|
|
||
|
memset(&m_request, 0, sizeof(OVERLAPPED));
|
||
|
m_request.Offset = m_header.Segments[HEADER::SEGIDX_ENTRYWAVEDATA].dwOffset;
|
||
|
m_request.hEvent = m_event.get();
|
||
|
|
||
|
if (!ReadFile(hFile.get(), dest, waveLen, nullptr, &m_request))
|
||
|
{
|
||
|
DWORD error = GetLastError();
|
||
|
if (error != ERROR_IO_PENDING)
|
||
|
return HRESULT_FROM_WIN32(error);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_prepared = true;
|
||
|
memset(&m_request, 0, sizeof(OVERLAPPED));
|
||
|
}
|
||
|
|
||
|
m_async = hFile.release();
|
||
|
}
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
void WaveBankReader::Impl::Close() noexcept
|
||
|
{
|
||
|
if (m_async != INVALID_HANDLE_VALUE)
|
||
|
{
|
||
|
if (m_request.hEvent)
|
||
|
{
|
||
|
DWORD bytes;
|
||
|
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
|
||
|
(void)GetOverlappedResultEx(m_async, &m_request, &bytes, INFINITE, FALSE);
|
||
|
#else
|
||
|
(void)WaitForSingleObject(m_request.hEvent, INFINITE);
|
||
|
|
||
|
(void)GetOverlappedResult(m_async, &m_request, &bytes, FALSE);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
CloseHandle(m_async);
|
||
|
m_async = INVALID_HANDLE_VALUE;
|
||
|
}
|
||
|
m_event.reset();
|
||
|
|
||
|
#ifdef DIRECTX_ENABLE_XMA2
|
||
|
if (m_xmaMemory)
|
||
|
{
|
||
|
ApuFree(m_xmaMemory);
|
||
|
m_xmaMemory = nullptr;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
_Use_decl_annotations_
|
||
|
HRESULT WaveBankReader::Impl::GetFormat(uint32_t index, WAVEFORMATEX* pFormat, size_t maxsize) const noexcept
|
||
|
{
|
||
|
if (!pFormat || !maxsize)
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
if (index >= m_data.dwEntryCount || !m_entries)
|
||
|
{
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
auto& miniFmt = (m_data.dwFlags & BANKDATA::FLAGS_COMPACT) ? m_data.CompactFormat : (reinterpret_cast<const ENTRY*>(m_entries.get())[index].Format);
|
||
|
|
||
|
switch (miniFmt.wFormatTag)
|
||
|
{
|
||
|
case MINIWAVEFORMAT::TAG_PCM:
|
||
|
if (maxsize < sizeof(PCMWAVEFORMAT))
|
||
|
return HRESULT_FROM_WIN32(ERROR_MORE_DATA);
|
||
|
|
||
|
pFormat->wFormatTag = WAVE_FORMAT_PCM;
|
||
|
|
||
|
if (maxsize >= sizeof(WAVEFORMATEX))
|
||
|
{
|
||
|
pFormat->cbSize = 0;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case MINIWAVEFORMAT::TAG_ADPCM:
|
||
|
if (maxsize < (sizeof(WAVEFORMATEX) + 32 /*MSADPCM_FORMAT_EXTRA_BYTES*/))
|
||
|
return HRESULT_FROM_WIN32(ERROR_MORE_DATA);
|
||
|
|
||
|
pFormat->wFormatTag = WAVE_FORMAT_ADPCM;
|
||
|
pFormat->cbSize = 32 /*MSADPCM_FORMAT_EXTRA_BYTES*/;
|
||
|
{
|
||
|
auto adpcmFmt = reinterpret_cast<ADPCMWAVEFORMAT*>(pFormat);
|
||
|
adpcmFmt->wSamplesPerBlock = static_cast<WORD>(miniFmt.AdpcmSamplesPerBlock());
|
||
|
miniFmt.AdpcmFillCoefficientTable(adpcmFmt);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case MINIWAVEFORMAT::TAG_WMA:
|
||
|
if (maxsize < sizeof(WAVEFORMATEX))
|
||
|
return HRESULT_FROM_WIN32(ERROR_MORE_DATA);
|
||
|
|
||
|
pFormat->wFormatTag = static_cast<WORD>((miniFmt.wBitsPerSample & 0x1) ? WAVE_FORMAT_WMAUDIO3 : WAVE_FORMAT_WMAUDIO2);
|
||
|
pFormat->cbSize = 0;
|
||
|
break;
|
||
|
|
||
|
case MINIWAVEFORMAT::TAG_XMA: // XMA2 is supported by Xbox One
|
||
|
#ifdef DIRECTX_ENABLE_XMA2
|
||
|
if (maxsize < sizeof(XMA2WAVEFORMATEX))
|
||
|
return HRESULT_FROM_WIN32(ERROR_MORE_DATA);
|
||
|
|
||
|
pFormat->wFormatTag = WAVE_FORMAT_XMA2;
|
||
|
pFormat->cbSize = sizeof(XMA2WAVEFORMATEX) - sizeof(WAVEFORMATEX);
|
||
|
{
|
||
|
auto xmaFmt = reinterpret_cast<XMA2WAVEFORMATEX*>(pFormat);
|
||
|
|
||
|
xmaFmt->NumStreams = static_cast<WORD>((miniFmt.nChannels + 1) / 2);
|
||
|
xmaFmt->BytesPerBlock = 65536 /* XACT_FIXED_XMA_BLOCK_SIZE */;
|
||
|
xmaFmt->EncoderVersion = 4 /* XMAENCODER_VERSION_XMA2 */;
|
||
|
|
||
|
auto seekTable = FindSeekTable(index, m_seekData.get(), m_header, m_data);
|
||
|
if (seekTable)
|
||
|
{
|
||
|
xmaFmt->BlockCount = static_cast<WORD>(*seekTable);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
xmaFmt->BlockCount = 0;
|
||
|
}
|
||
|
|
||
|
switch (miniFmt.nChannels)
|
||
|
{
|
||
|
case 1: xmaFmt->ChannelMask = SPEAKER_MONO; break;
|
||
|
case 2: xmaFmt->ChannelMask = SPEAKER_STEREO; break;
|
||
|
case 3: xmaFmt->ChannelMask = SPEAKER_2POINT1; break;
|
||
|
case 4: xmaFmt->ChannelMask = SPEAKER_QUAD; break;
|
||
|
case 5: xmaFmt->ChannelMask = SPEAKER_4POINT1; break;
|
||
|
case 6: xmaFmt->ChannelMask = SPEAKER_5POINT1; break;
|
||
|
case 7: xmaFmt->ChannelMask = SPEAKER_5POINT1 | SPEAKER_BACK_CENTER; break;
|
||
|
case 8: xmaFmt->ChannelMask = SPEAKER_7POINT1; break;
|
||
|
default: xmaFmt->ChannelMask = DWORD(-1); break;
|
||
|
}
|
||
|
|
||
|
if (m_data.dwFlags & BANKDATA::FLAGS_COMPACT)
|
||
|
{
|
||
|
auto& entry = reinterpret_cast<const ENTRYCOMPACT*>(m_entries.get())[index];
|
||
|
|
||
|
DWORD dwOffset, dwLength;
|
||
|
entry.ComputeLocations(dwOffset, dwLength, index, m_header, m_data, reinterpret_cast<const ENTRYCOMPACT*>(m_entries.get()));
|
||
|
|
||
|
xmaFmt->SamplesEncoded = entry.GetDuration(dwLength, m_data, seekTable);
|
||
|
|
||
|
xmaFmt->PlayBegin = xmaFmt->PlayLength =
|
||
|
xmaFmt->LoopBegin = xmaFmt->LoopLength = xmaFmt->LoopCount = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
auto& entry = reinterpret_cast<const ENTRY*>(m_entries.get())[index];
|
||
|
|
||
|
xmaFmt->SamplesEncoded = entry.Duration;
|
||
|
xmaFmt->PlayBegin = 0;
|
||
|
xmaFmt->PlayLength = entry.PlayRegion.dwLength;
|
||
|
|
||
|
if (entry.LoopRegion.dwTotalSamples > 0)
|
||
|
{
|
||
|
xmaFmt->LoopBegin = entry.LoopRegion.dwStartSample;
|
||
|
xmaFmt->LoopLength = entry.LoopRegion.dwTotalSamples;
|
||
|
xmaFmt->LoopCount = 0xff /* XACTLOOPCOUNT_INFINITE */;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
xmaFmt->LoopBegin = xmaFmt->LoopLength = xmaFmt->LoopCount = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
#else
|
||
|
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
|
||
|
#endif
|
||
|
|
||
|
default:
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
pFormat->nChannels = miniFmt.nChannels;
|
||
|
pFormat->wBitsPerSample = miniFmt.BitsPerSample();
|
||
|
pFormat->nBlockAlign = static_cast<WORD>(miniFmt.BlockAlign());
|
||
|
pFormat->nSamplesPerSec = miniFmt.nSamplesPerSec;
|
||
|
pFormat->nAvgBytesPerSec = miniFmt.AvgBytesPerSec();
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
_Use_decl_annotations_
|
||
|
HRESULT WaveBankReader::Impl::GetWaveData(uint32_t index, const uint8_t** pData, uint32_t& dataSize) const noexcept
|
||
|
{
|
||
|
if (!pData)
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
if (index >= m_data.dwEntryCount || !m_entries)
|
||
|
{
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
#ifdef DIRECTX_ENABLE_XMA2
|
||
|
const uint8_t* waveData = (m_xmaMemory) ? reinterpret_cast<uint8_t*>(m_xmaMemory) : m_waveData.get();
|
||
|
#else
|
||
|
const uint8_t* waveData = m_waveData.get();
|
||
|
#endif
|
||
|
|
||
|
if (!waveData)
|
||
|
return E_FAIL;
|
||
|
|
||
|
if (m_data.dwFlags & BANKDATA::TYPE_STREAMING)
|
||
|
{
|
||
|
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
|
||
|
}
|
||
|
|
||
|
if (!m_prepared)
|
||
|
{
|
||
|
return HRESULT_FROM_WIN32(ERROR_IO_INCOMPLETE);
|
||
|
}
|
||
|
|
||
|
if (m_data.dwFlags & BANKDATA::FLAGS_COMPACT)
|
||
|
{
|
||
|
auto& entry = reinterpret_cast<const ENTRYCOMPACT*>(m_entries.get())[index];
|
||
|
|
||
|
DWORD dwOffset, dwLength;
|
||
|
entry.ComputeLocations(dwOffset, dwLength, index, m_header, m_data, reinterpret_cast<const ENTRYCOMPACT*>(m_entries.get()));
|
||
|
|
||
|
if ((uint64_t(dwOffset) + uint64_t(dwLength)) > uint64_t(m_header.Segments[HEADER::SEGIDX_ENTRYWAVEDATA].dwLength))
|
||
|
{
|
||
|
return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
|
||
|
}
|
||
|
|
||
|
*pData = &waveData[dwOffset];
|
||
|
dataSize = dwLength;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
auto& entry = reinterpret_cast<const ENTRY*>(m_entries.get())[index];
|
||
|
|
||
|
if ((uint64_t(entry.PlayRegion.dwOffset) + uint64_t(entry.PlayRegion.dwLength)) > uint64_t(m_header.Segments[HEADER::SEGIDX_ENTRYWAVEDATA].dwLength))
|
||
|
{
|
||
|
return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
|
||
|
}
|
||
|
|
||
|
*pData = &waveData[entry.PlayRegion.dwOffset];
|
||
|
dataSize = entry.PlayRegion.dwLength;
|
||
|
}
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
_Use_decl_annotations_
|
||
|
HRESULT WaveBankReader::Impl::GetSeekTable(uint32_t index, const uint32_t** pData, uint32_t& dataCount, uint32_t& tag) const noexcept
|
||
|
{
|
||
|
if (!pData)
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
*pData = nullptr;
|
||
|
dataCount = 0;
|
||
|
tag = 0;
|
||
|
|
||
|
if (index >= m_data.dwEntryCount || !m_entries)
|
||
|
{
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
if (!m_seekData)
|
||
|
return S_OK;
|
||
|
|
||
|
auto& miniFmt = (m_data.dwFlags & BANKDATA::FLAGS_COMPACT) ? m_data.CompactFormat : (reinterpret_cast<const ENTRY*>(m_entries.get())[index].Format);
|
||
|
|
||
|
switch (miniFmt.wFormatTag)
|
||
|
{
|
||
|
case MINIWAVEFORMAT::TAG_WMA:
|
||
|
tag = static_cast<uint32_t>((miniFmt.wBitsPerSample & 0x1) ? WAVE_FORMAT_WMAUDIO3 : WAVE_FORMAT_WMAUDIO2);
|
||
|
break;
|
||
|
|
||
|
case MINIWAVEFORMAT::TAG_XMA:
|
||
|
tag = 0x166 /* WAVE_FORMAT_XMA2 */;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
auto seekTable = FindSeekTable(index, m_seekData.get(), m_header, m_data);
|
||
|
if (!seekTable)
|
||
|
return S_OK;
|
||
|
|
||
|
dataCount = *seekTable;
|
||
|
*pData = seekTable + 1;
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
_Use_decl_annotations_
|
||
|
HRESULT WaveBankReader::Impl::GetMetadata(uint32_t index, Metadata& metadata) const noexcept
|
||
|
{
|
||
|
if (index >= m_data.dwEntryCount || !m_entries)
|
||
|
{
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
if (m_data.dwFlags & BANKDATA::FLAGS_COMPACT)
|
||
|
{
|
||
|
auto& entry = reinterpret_cast<const ENTRYCOMPACT*>(m_entries.get())[index];
|
||
|
|
||
|
DWORD dwOffset, dwLength;
|
||
|
entry.ComputeLocations(dwOffset, dwLength, index, m_header, m_data, reinterpret_cast<const ENTRYCOMPACT*>(m_entries.get()));
|
||
|
|
||
|
auto seekTable = FindSeekTable(index, m_seekData.get(), m_header, m_data);
|
||
|
metadata.duration = entry.GetDuration(dwLength, m_data, seekTable);
|
||
|
metadata.loopStart = metadata.loopLength = 0;
|
||
|
metadata.offsetBytes = dwOffset;
|
||
|
metadata.lengthBytes = dwLength;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
auto& entry = reinterpret_cast<const ENTRY*>(m_entries.get())[index];
|
||
|
|
||
|
metadata.duration = entry.Duration;
|
||
|
metadata.loopStart = entry.LoopRegion.dwStartSample;
|
||
|
metadata.loopLength = entry.LoopRegion.dwTotalSamples;
|
||
|
metadata.offsetBytes = entry.PlayRegion.dwOffset;
|
||
|
metadata.lengthBytes = entry.PlayRegion.dwLength;
|
||
|
}
|
||
|
|
||
|
if (m_data.dwFlags & BANKDATA::TYPE_STREAMING)
|
||
|
{
|
||
|
uint64_t offset = uint64_t(metadata.offsetBytes) + uint64_t(m_header.Segments[HEADER::SEGIDX_ENTRYWAVEDATA].dwOffset);
|
||
|
if (offset > UINT32_MAX)
|
||
|
return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);
|
||
|
|
||
|
metadata.offsetBytes = static_cast<uint32_t>(offset);
|
||
|
}
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
bool WaveBankReader::Impl::UpdatePrepared() noexcept
|
||
|
{
|
||
|
if (m_prepared)
|
||
|
return true;
|
||
|
|
||
|
if (m_async == INVALID_HANDLE_VALUE)
|
||
|
return false;
|
||
|
|
||
|
if (m_request.hEvent)
|
||
|
{
|
||
|
|
||
|
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
|
||
|
DWORD bytes;
|
||
|
BOOL result = GetOverlappedResultEx(m_async, &m_request, &bytes, 0, FALSE);
|
||
|
#else
|
||
|
bool result = HasOverlappedIoCompleted(&m_request);
|
||
|
#endif
|
||
|
if (result)
|
||
|
{
|
||
|
m_prepared = true;
|
||
|
|
||
|
memset(&m_request, 0, sizeof(OVERLAPPED));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return m_prepared;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------
|
||
|
WaveBankReader::WaveBankReader() noexcept(false) :
|
||
|
pImpl(std::make_unique<Impl>())
|
||
|
{
|
||
|
}
|
||
|
|
||
|
|
||
|
WaveBankReader::~WaveBankReader()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
|
||
|
_Use_decl_annotations_
|
||
|
HRESULT WaveBankReader::Open(const wchar_t* szFileName) noexcept
|
||
|
{
|
||
|
return pImpl->Open(szFileName);
|
||
|
}
|
||
|
|
||
|
|
||
|
_Use_decl_annotations_
|
||
|
uint32_t WaveBankReader::Find(const char* name) const
|
||
|
{
|
||
|
auto it = pImpl->m_names.find(name);
|
||
|
if (it != pImpl->m_names.cend())
|
||
|
{
|
||
|
return it->second;
|
||
|
}
|
||
|
|
||
|
return uint32_t(-1);
|
||
|
}
|
||
|
|
||
|
|
||
|
bool WaveBankReader::IsPrepared() noexcept
|
||
|
{
|
||
|
if (pImpl->m_prepared)
|
||
|
return true;
|
||
|
|
||
|
return pImpl->UpdatePrepared();
|
||
|
}
|
||
|
|
||
|
|
||
|
void WaveBankReader::WaitOnPrepare() noexcept
|
||
|
{
|
||
|
if (pImpl->m_prepared)
|
||
|
return;
|
||
|
|
||
|
if (pImpl->m_request.hEvent)
|
||
|
{
|
||
|
(void)WaitForSingleObjectEx(pImpl->m_request.hEvent, INFINITE, FALSE);
|
||
|
|
||
|
pImpl->UpdatePrepared();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
bool WaveBankReader::HasNames() const noexcept
|
||
|
{
|
||
|
return !pImpl->m_names.empty();
|
||
|
}
|
||
|
|
||
|
|
||
|
bool WaveBankReader::IsStreamingBank() const noexcept
|
||
|
{
|
||
|
return (pImpl->m_data.dwFlags & BANKDATA::TYPE_STREAMING) != 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
#ifdef DIRECTX_ENABLE_XMA2
|
||
|
bool WaveBankReader::HasXMA() const noexcept
|
||
|
{
|
||
|
return (pImpl->m_xmaMemory != nullptr);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
const char* WaveBankReader::BankName() const noexcept
|
||
|
{
|
||
|
return pImpl->m_data.szBankName;
|
||
|
}
|
||
|
|
||
|
|
||
|
uint32_t WaveBankReader::Count() const noexcept
|
||
|
{
|
||
|
return pImpl->m_data.dwEntryCount;
|
||
|
}
|
||
|
|
||
|
|
||
|
uint32_t WaveBankReader::BankAudioSize() const noexcept
|
||
|
{
|
||
|
return pImpl->m_header.Segments[HEADER::SEGIDX_ENTRYWAVEDATA].dwLength;
|
||
|
}
|
||
|
|
||
|
|
||
|
_Use_decl_annotations_
|
||
|
HRESULT WaveBankReader::GetFormat(uint32_t index, WAVEFORMATEX* pFormat, size_t maxsize) const noexcept
|
||
|
{
|
||
|
return pImpl->GetFormat(index, pFormat, maxsize);
|
||
|
}
|
||
|
|
||
|
|
||
|
_Use_decl_annotations_
|
||
|
HRESULT WaveBankReader::GetWaveData(uint32_t index, const uint8_t** pData, uint32_t& dataSize) const noexcept
|
||
|
{
|
||
|
return pImpl->GetWaveData(index, pData, dataSize);
|
||
|
}
|
||
|
|
||
|
|
||
|
_Use_decl_annotations_
|
||
|
HRESULT WaveBankReader::GetSeekTable(uint32_t index, const uint32_t** pData, uint32_t& dataCount, uint32_t& tag) const noexcept
|
||
|
{
|
||
|
return pImpl->GetSeekTable(index, pData, dataCount, tag);
|
||
|
}
|
||
|
|
||
|
|
||
|
_Use_decl_annotations_
|
||
|
HRESULT WaveBankReader::GetMetadata(uint32_t index, Metadata& metadata) const noexcept
|
||
|
{
|
||
|
return pImpl->GetMetadata(index, metadata);
|
||
|
}
|
||
|
|
||
|
|
||
|
HANDLE WaveBankReader::GetAsyncHandle() const noexcept
|
||
|
{
|
||
|
return (pImpl->m_data.dwFlags & BANKDATA::TYPE_STREAMING) ? pImpl->m_async : INVALID_HANDLE_VALUE;
|
||
|
}
|