//-------------------------------------------------------------------------------------- // 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(wfx); if (memcmp(reinterpret_cast(&wfex->SubFormat) + sizeof(DWORD), reinterpret_cast(&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(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; }; }