mirror of
https://github.com/ncblakely/GiantsTools
synced 2024-12-23 15:57:22 +01:00
384 lines
11 KiB
C++
384 lines
11 KiB
C++
//--------------------------------------------------------------------------------------
|
|
// File: SoundCommon.h
|
|
//
|
|
// 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
|
|
//--------------------------------------------------------------------------------------
|
|
|
|
#pragma once
|
|
|
|
#include "Audio.h"
|
|
#include "PlatformHelpers.h"
|
|
|
|
#ifdef USING_XAUDIO2_9
|
|
#define DIRECTX_ENABLE_XWMA
|
|
#endif
|
|
|
|
#if (defined(_XBOX_ONE) && defined(_TITLE)) || defined(_GAMING_XBOX)
|
|
#define DIRECTX_ENABLE_XMA2
|
|
#endif
|
|
|
|
#if defined(DIRECTX_ENABLE_XWMA) || defined(DIRECTX_ENABLE_XMA2)
|
|
#define DIRECTX_ENABLE_SEEK_TABLES
|
|
#endif
|
|
|
|
namespace DirectX
|
|
{
|
|
// Helper for getting a format tag from a WAVEFORMATEX
|
|
inline uint32_t GetFormatTag(const WAVEFORMATEX* wfx) noexcept
|
|
{
|
|
if (wfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
|
|
{
|
|
if (wfx->cbSize < (sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)))
|
|
return 0;
|
|
|
|
static const GUID s_wfexBase = { 0x00000000, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 } };
|
|
|
|
auto wfex = reinterpret_cast<const WAVEFORMATEXTENSIBLE*>(wfx);
|
|
|
|
if (memcmp(reinterpret_cast<const BYTE*>(&wfex->SubFormat) + sizeof(DWORD),
|
|
reinterpret_cast<const BYTE*>(&s_wfexBase) + sizeof(DWORD), sizeof(GUID) - sizeof(DWORD)) != 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return wfex->SubFormat.Data1;
|
|
}
|
|
else
|
|
{
|
|
return wfx->wFormatTag;
|
|
}
|
|
}
|
|
|
|
|
|
// Helper for validating wave format structure
|
|
bool IsValid(_In_ const WAVEFORMATEX* wfx) noexcept;
|
|
|
|
|
|
// Helper for getting a default channel mask from channels
|
|
uint32_t GetDefaultChannelMask(int channels) noexcept;
|
|
|
|
|
|
// Helpers for creating various wave format structures
|
|
void CreateIntegerPCM(_Out_ WAVEFORMATEX* wfx, int sampleRate, int channels, int sampleBits) noexcept;
|
|
void CreateFloatPCM(_Out_ WAVEFORMATEX* wfx, int sampleRate, int channels) noexcept;
|
|
void CreateADPCM(_Out_writes_bytes_(wfxSize) WAVEFORMATEX* wfx, size_t wfxSize, int sampleRate, int channels, int samplesPerBlock) noexcept(false);
|
|
#ifdef DIRECTX_ENABLE_XWMA
|
|
void CreateXWMA(_Out_ WAVEFORMATEX* wfx, int sampleRate, int channels, int blockAlign, int avgBytes, bool wma3) noexcept;
|
|
#endif
|
|
#ifdef DIRECTX_ENABLE_XMA2
|
|
void CreateXMA2(_Out_writes_bytes_(wfxSize) WAVEFORMATEX* wfx, size_t wfxSize, int sampleRate, int channels, int bytesPerBlock, int blockCount, int samplesEncoded) noexcept(false);
|
|
#endif
|
|
|
|
// Helper for computing pan volume matrix
|
|
bool ComputePan(float pan, unsigned int channels, _Out_writes_(16) float* matrix) noexcept;
|
|
|
|
// Helper class for implementing SoundEffectInstance
|
|
class SoundEffectInstanceBase
|
|
{
|
|
public:
|
|
SoundEffectInstanceBase() noexcept :
|
|
voice(nullptr),
|
|
state(STOPPED),
|
|
engine(nullptr),
|
|
mVolume(1.f),
|
|
mPitch(0.f),
|
|
mFreqRatio(1.f),
|
|
mPan(0.f),
|
|
mFlags(SoundEffectInstance_Default),
|
|
mDirectVoice(nullptr),
|
|
mReverbVoice(nullptr),
|
|
mDSPSettings{}
|
|
{
|
|
}
|
|
|
|
SoundEffectInstanceBase(SoundEffectInstanceBase&&) = default;
|
|
SoundEffectInstanceBase& operator= (SoundEffectInstanceBase&&) = default;
|
|
|
|
SoundEffectInstanceBase(SoundEffectInstanceBase const&) = delete;
|
|
SoundEffectInstanceBase& operator= (SoundEffectInstanceBase const&) = delete;
|
|
|
|
~SoundEffectInstanceBase()
|
|
{
|
|
assert(voice == nullptr);
|
|
}
|
|
|
|
void Initialize(_In_ AudioEngine* eng, _In_ const WAVEFORMATEX* wfx, SOUND_EFFECT_INSTANCE_FLAGS flags) noexcept
|
|
{
|
|
assert(eng != nullptr);
|
|
engine = eng;
|
|
mDirectVoice = eng->GetMasterVoice();
|
|
mReverbVoice = eng->GetReverbVoice();
|
|
|
|
if (eng->GetChannelMask() & SPEAKER_LOW_FREQUENCY)
|
|
mFlags = flags | SoundEffectInstance_UseRedirectLFE;
|
|
else
|
|
mFlags = flags & ~SoundEffectInstance_UseRedirectLFE;
|
|
|
|
memset(&mDSPSettings, 0, sizeof(X3DAUDIO_DSP_SETTINGS));
|
|
assert(wfx != nullptr);
|
|
mDSPSettings.SrcChannelCount = wfx->nChannels;
|
|
mDSPSettings.DstChannelCount = eng->GetOutputChannels();
|
|
}
|
|
|
|
void AllocateVoice(_In_ const WAVEFORMATEX* wfx)
|
|
{
|
|
if (voice)
|
|
return;
|
|
|
|
assert(engine != nullptr);
|
|
engine->AllocateVoice(wfx, mFlags, false, &voice);
|
|
}
|
|
|
|
void DestroyVoice() noexcept
|
|
{
|
|
if (voice)
|
|
{
|
|
assert(engine != nullptr);
|
|
engine->DestroyVoice(voice);
|
|
voice = nullptr;
|
|
}
|
|
}
|
|
|
|
bool Play() // Returns true if STOPPED -> PLAYING
|
|
{
|
|
if (voice)
|
|
{
|
|
if (state == PAUSED)
|
|
{
|
|
HRESULT hr = voice->Start(0);
|
|
ThrowIfFailed(hr);
|
|
state = PLAYING;
|
|
}
|
|
else if (state != PLAYING)
|
|
{
|
|
if (mVolume != 1.f)
|
|
{
|
|
HRESULT hr = voice->SetVolume(mVolume);
|
|
ThrowIfFailed(hr);
|
|
}
|
|
|
|
if (mPitch != 0.f)
|
|
{
|
|
mFreqRatio = XAudio2SemitonesToFrequencyRatio(mPitch * 12.f);
|
|
|
|
HRESULT hr = voice->SetFrequencyRatio(mFreqRatio);
|
|
ThrowIfFailed(hr);
|
|
}
|
|
|
|
if (mPan != 0.f)
|
|
{
|
|
SetPan(mPan);
|
|
}
|
|
|
|
HRESULT hr = voice->Start(0);
|
|
ThrowIfFailed(hr);
|
|
state = PLAYING;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Stop(bool immediate, bool& looped) noexcept
|
|
{
|
|
if (!voice)
|
|
{
|
|
state = STOPPED;
|
|
return;
|
|
}
|
|
|
|
if (immediate)
|
|
{
|
|
state = STOPPED;
|
|
(void)voice->Stop(0);
|
|
(void)voice->FlushSourceBuffers();
|
|
}
|
|
else if (looped)
|
|
{
|
|
looped = false;
|
|
(void)voice->ExitLoop();
|
|
}
|
|
else
|
|
{
|
|
(void)voice->Stop(XAUDIO2_PLAY_TAILS);
|
|
}
|
|
}
|
|
|
|
void Pause() noexcept
|
|
{
|
|
if (voice && state == PLAYING)
|
|
{
|
|
state = PAUSED;
|
|
|
|
(void)voice->Stop(0);
|
|
}
|
|
}
|
|
|
|
void Resume()
|
|
{
|
|
if (voice && state == PAUSED)
|
|
{
|
|
HRESULT hr = voice->Start(0);
|
|
ThrowIfFailed(hr);
|
|
state = PLAYING;
|
|
}
|
|
}
|
|
|
|
void SetVolume(float volume)
|
|
{
|
|
assert(volume >= -XAUDIO2_MAX_VOLUME_LEVEL && volume <= XAUDIO2_MAX_VOLUME_LEVEL);
|
|
|
|
mVolume = volume;
|
|
|
|
if (voice)
|
|
{
|
|
HRESULT hr = voice->SetVolume(volume);
|
|
ThrowIfFailed(hr);
|
|
}
|
|
}
|
|
|
|
void SetPitch(float pitch)
|
|
{
|
|
assert(pitch >= -1.f && pitch <= 1.f);
|
|
|
|
if ((mFlags & SoundEffectInstance_NoSetPitch) && pitch != 0.f)
|
|
{
|
|
DebugTrace("ERROR: Sound effect instance was created with the NoSetPitch flag\n");
|
|
throw std::exception("SetPitch");
|
|
}
|
|
|
|
mPitch = pitch;
|
|
|
|
if (voice)
|
|
{
|
|
mFreqRatio = XAudio2SemitonesToFrequencyRatio(mPitch * 12.f);
|
|
|
|
HRESULT hr = voice->SetFrequencyRatio(mFreqRatio);
|
|
ThrowIfFailed(hr);
|
|
}
|
|
}
|
|
|
|
void SetPan(float pan);
|
|
|
|
void Apply3D(const AudioListener& listener, const AudioEmitter& emitter, bool rhcoords);
|
|
|
|
SoundState GetState(bool autostop) noexcept
|
|
{
|
|
if (autostop && voice && (state == PLAYING))
|
|
{
|
|
XAUDIO2_VOICE_STATE xstate;
|
|
voice->GetState(&xstate, XAUDIO2_VOICE_NOSAMPLESPLAYED);
|
|
|
|
if (!xstate.BuffersQueued)
|
|
{
|
|
// Automatic stop if the buffer has finished playing
|
|
(void)voice->Stop();
|
|
state = STOPPED;
|
|
}
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
int GetPendingBufferCount() const noexcept
|
|
{
|
|
if (!voice)
|
|
return 0;
|
|
|
|
XAUDIO2_VOICE_STATE xstate;
|
|
voice->GetState(&xstate, XAUDIO2_VOICE_NOSAMPLESPLAYED);
|
|
return static_cast<int>(xstate.BuffersQueued);
|
|
}
|
|
|
|
void OnCriticalError() noexcept
|
|
{
|
|
if (voice)
|
|
{
|
|
voice->DestroyVoice();
|
|
voice = nullptr;
|
|
}
|
|
state = STOPPED;
|
|
mDirectVoice = nullptr;
|
|
mReverbVoice = nullptr;
|
|
}
|
|
|
|
void OnReset() noexcept
|
|
{
|
|
assert(engine != nullptr);
|
|
mDirectVoice = engine->GetMasterVoice();
|
|
mReverbVoice = engine->GetReverbVoice();
|
|
|
|
if (engine->GetChannelMask() & SPEAKER_LOW_FREQUENCY)
|
|
mFlags = mFlags | SoundEffectInstance_UseRedirectLFE;
|
|
else
|
|
mFlags = mFlags & ~SoundEffectInstance_UseRedirectLFE;
|
|
|
|
mDSPSettings.DstChannelCount = engine->GetOutputChannels();
|
|
}
|
|
|
|
void OnDestroy() noexcept
|
|
{
|
|
if (voice)
|
|
{
|
|
(void)voice->Stop(0);
|
|
(void)voice->FlushSourceBuffers();
|
|
voice->DestroyVoice();
|
|
voice = nullptr;
|
|
}
|
|
state = STOPPED;
|
|
engine = nullptr;
|
|
mDirectVoice = nullptr;
|
|
mReverbVoice = nullptr;
|
|
}
|
|
|
|
void OnTrim()
|
|
{
|
|
if (voice && (state == STOPPED))
|
|
{
|
|
engine->DestroyVoice(voice);
|
|
voice = nullptr;
|
|
}
|
|
}
|
|
|
|
void GatherStatistics(AudioStatistics& stats) const noexcept
|
|
{
|
|
++stats.allocatedInstances;
|
|
if (voice)
|
|
{
|
|
++stats.allocatedVoices;
|
|
|
|
if (mFlags & SoundEffectInstance_Use3D)
|
|
++stats.allocatedVoices3d;
|
|
|
|
if (state == PLAYING)
|
|
++stats.playingInstances;
|
|
}
|
|
}
|
|
|
|
IXAudio2SourceVoice* voice;
|
|
SoundState state;
|
|
AudioEngine* engine;
|
|
|
|
private:
|
|
float mVolume;
|
|
float mPitch;
|
|
float mFreqRatio;
|
|
float mPan;
|
|
SOUND_EFFECT_INSTANCE_FLAGS mFlags;
|
|
IXAudio2Voice* mDirectVoice;
|
|
IXAudio2Voice* mReverbVoice;
|
|
X3DAUDIO_DSP_SETTINGS mDSPSettings;
|
|
};
|
|
|
|
struct WaveBankSeekData
|
|
{
|
|
uint32_t seekCount;
|
|
const uint32_t* seekTable;
|
|
uint32_t tag;
|
|
};
|
|
}
|