//-------------------------------------------------------------------------------------- // File: WaveBank.cpp // // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved. // // http://go.microsoft.com/fwlink/?LinkId=248929 //-------------------------------------------------------------------------------------- #include "pch.h" #include "Audio.h" #include "WaveBankReader.h" #include "SoundCommon.h" #include "PlatformHelpers.h" #include using namespace DirectX; //====================================================================================== // WaveBank //====================================================================================== // Internal object implementation class. class WaveBank::Impl : public IVoiceNotify { public: explicit Impl( _In_ AudioEngine* engine ) : mEngine( engine ), mOneShots( 0 ), mPrepared( false ), mStreaming( false ) { assert( mEngine != 0 ); mEngine->RegisterNotify( this, false ); } virtual ~Impl() { if ( !mInstances.empty() ) { DebugTrace( "WARNING: Destroying WaveBank \"%hs\" with %Iu outstanding SoundEffectInstances\n", mReader.BankName(), mInstances.size() ); for( auto it = mInstances.begin(); it != mInstances.end(); ++it ) { assert( *it != 0 ); (*it)->OnDestroyParent(); } mInstances.clear(); } if ( mOneShots > 0 ) { DebugTrace( "WARNING: Destroying WaveBank \"%hs\" with %u outstanding one shot effects\n", mReader.BankName(), mOneShots ); } if ( mEngine ) { mEngine->UnregisterNotify( this, true, false ); mEngine = nullptr; } } HRESULT Initialize( _In_ AudioEngine* engine, _In_z_ const wchar_t* wbFileName ); void Play( int index, float volume, float pitch, float pan ); // IVoiceNotify virtual void __cdecl OnBufferEnd() override { InterlockedDecrement( &mOneShots ); } virtual void __cdecl OnCriticalError() override { mOneShots = 0; } virtual void __cdecl OnReset() override { // No action required } virtual void __cdecl OnUpdate() override { // We do not register for update notification assert(false); } virtual void __cdecl OnDestroyEngine() override { mEngine = nullptr; mOneShots = 0; } virtual void __cdecl OnTrim() override { // No action required } virtual void __cdecl GatherStatistics( AudioStatistics& stats ) const override { stats.playingOneShots += mOneShots; if ( !mStreaming ) { stats.audioBytes += mReader.BankAudioSize(); #if defined(_XBOX_ONE) && defined(_TITLE) if ( mReader.HasXMA() ) stats.xmaAudioBytes += mReader.BankAudioSize(); #endif } } AudioEngine* mEngine; std::list mInstances; WaveBankReader mReader; uint32_t mOneShots; bool mPrepared; bool mStreaming; }; _Use_decl_annotations_ HRESULT WaveBank::Impl::Initialize( AudioEngine* engine, const wchar_t* wbFileName ) { if ( !engine || !wbFileName ) return E_INVALIDARG; HRESULT hr = mReader.Open( wbFileName ); if ( FAILED(hr) ) return hr; mStreaming = mReader.IsStreamingBank(); return S_OK; } void WaveBank::Impl::Play( int index, float volume, float pitch, float pan ) { assert( volume >= -XAUDIO2_MAX_VOLUME_LEVEL && volume <= XAUDIO2_MAX_VOLUME_LEVEL ); assert( pitch >= -1.f && pitch <= 1.f ); assert( pan >= -1.f && pan <= 1.f ); if ( mStreaming ) { DebugTrace( "ERROR: One-shots can only be created from an in-memory wave bank\n"); throw std::exception( "WaveBank::Play" ); } if ( index < 0 || uint32_t(index) >= mReader.Count() ) { DebugTrace( "WARNING: Index %d not found in wave bank with only %u entries, one-shot not triggered\n", index, mReader.Count() ); return; } if ( !mPrepared ) { mReader.WaitOnPrepare(); mPrepared = true; } char wfxbuff[64]; auto wfx = reinterpret_cast( wfxbuff ); HRESULT hr = mReader.GetFormat( index, wfx, 64 ); ThrowIfFailed( hr ); IXAudio2SourceVoice* voice = nullptr; mEngine->AllocateVoice( wfx, SoundEffectInstance_Default, true, &voice ); if ( !voice ) return; if ( volume != 1.f ) { hr = voice->SetVolume( volume ); ThrowIfFailed( hr ); } if ( pitch != 0.f ) { float fr = XAudio2SemitonesToFrequencyRatio( pitch * 12.f ); hr = voice->SetFrequencyRatio( fr ); ThrowIfFailed( hr ); } if ( pan != 0.f ) { float matrix[16]; if ( ComputePan( pan, wfx->nChannels, matrix ) ) { hr = voice->SetOutputMatrix( nullptr, wfx->nChannels, mEngine->GetOutputChannels(), matrix ); ThrowIfFailed( hr ); } } hr = voice->Start( 0 ); ThrowIfFailed( hr ); XAUDIO2_BUFFER buffer; memset( &buffer, 0, sizeof(buffer) ); hr = mReader.GetWaveData( index, &buffer.pAudioData, buffer.AudioBytes ); ThrowIfFailed( hr ); WaveBankReader::Metadata metadata; hr = mReader.GetMetadata( index, metadata ); ThrowIfFailed( hr ); buffer.Flags = XAUDIO2_END_OF_STREAM; buffer.pContext = this; #if defined(_XBOX_ONE) || (_WIN32_WINNT < _WIN32_WINNT_WIN8) || (_WIN32_WINNT >= _WIN32_WINNT_WIN10) XAUDIO2_BUFFER_WMA wmaBuffer; memset( &wmaBuffer, 0, sizeof(wmaBuffer) ); uint32_t tag; hr = mReader.GetSeekTable( index, &wmaBuffer.pDecodedPacketCumulativeBytes, wmaBuffer.PacketCount, tag ); ThrowIfFailed( hr ); if ( tag == WAVE_FORMAT_WMAUDIO2 || tag == WAVE_FORMAT_WMAUDIO3 ) { hr = voice->SubmitSourceBuffer( &buffer, &wmaBuffer ); } else #endif { hr = voice->SubmitSourceBuffer( &buffer, nullptr ); } if ( FAILED(hr) ) { DebugTrace( "ERROR: WaveBank failed (%08X) when submitting buffer:\n", hr ); DebugTrace( "\tFormat Tag %u, %u channels, %u-bit, %u Hz, %u bytes\n", wfx->wFormatTag, wfx->nChannels, wfx->wBitsPerSample, wfx->nSamplesPerSec, metadata.lengthBytes ); throw std::exception( "SubmitSourceBuffer" ); } InterlockedIncrement( &mOneShots ); } //-------------------------------------------------------------------------------------- // WaveBank //-------------------------------------------------------------------------------------- // Public constructors. _Use_decl_annotations_ WaveBank::WaveBank( AudioEngine* engine, const wchar_t* wbFileName ) : pImpl(new Impl(engine) ) { HRESULT hr = pImpl->Initialize( engine, wbFileName ); if ( FAILED(hr) ) { DebugTrace( "ERROR: WaveBank failed (%08X) to intialize from .xwb file \"%ls\"\n", hr, wbFileName ); throw std::exception( "WaveBank" ); } DebugTrace( "INFO: WaveBank \"%hs\" with %u entries loaded from .xwb file \"%ls\"\n", pImpl->mReader.BankName(), pImpl->mReader.Count(), wbFileName ); } // Move constructor. WaveBank::WaveBank(WaveBank&& moveFrom) : pImpl(std::move(moveFrom.pImpl)) { } // Move assignment. WaveBank& WaveBank::operator= (WaveBank&& moveFrom) { pImpl = std::move(moveFrom.pImpl); return *this; } // Public destructor. WaveBank::~WaveBank() { } // Public methods. void WaveBank::Play( int index ) { pImpl->Play( index, 1.f, 0.f, 0.f ); } void WaveBank::Play( int index, float volume, float pitch, float pan ) { pImpl->Play( index, volume, pitch, pan ); } void WaveBank::Play( _In_z_ const char* name ) { int index = static_cast( pImpl->mReader.Find( name ) ); if ( index == -1 ) { DebugTrace( "WARNING: Name '%hs' not found in wave bank, one-shot not triggered\n", name ); return; } pImpl->Play( index, 1.f, 0.f, 0.f ); } void WaveBank::Play( _In_z_ const char* name, float volume, float pitch, float pan ) { int index = static_cast( pImpl->mReader.Find( name ) ); if ( index == -1 ) { DebugTrace( "WARNING: Name '%hs' not found in wave bank, one-shot not triggered\n", name ); return; } pImpl->Play( index, volume, pitch, pan ); } std::unique_ptr WaveBank::CreateInstance( int index, SOUND_EFFECT_INSTANCE_FLAGS flags ) { auto& wb = pImpl->mReader; if ( pImpl->mStreaming ) { DebugTrace( "ERROR: SoundEffectInstances can only be created from an in-memory wave bank\n"); throw std::exception( "WaveBank::CreateInstance" ); } if ( index < 0 || uint32_t(index) >= wb.Count() ) { // We don't throw an exception here as titles often simply ignore missing assets rather than fail return std::unique_ptr(); } if ( !pImpl->mPrepared ) { wb.WaitOnPrepare(); pImpl->mPrepared = true; } auto effect = new SoundEffectInstance( pImpl->mEngine, this, index, flags ); assert( effect != 0 ); pImpl->mInstances.emplace_back( effect ); return std::unique_ptr( effect ); } std::unique_ptr WaveBank::CreateInstance( _In_z_ const char* name, SOUND_EFFECT_INSTANCE_FLAGS flags ) { int index = static_cast( pImpl->mReader.Find( name ) ); if ( index == -1 ) { // We don't throw an exception here as titles often simply ignore missing assets rather than fail return std::unique_ptr(); } return CreateInstance( index, flags ); } void WaveBank::UnregisterInstance( _In_ SoundEffectInstance* instance ) { auto it = std::find( pImpl->mInstances.begin(), pImpl->mInstances.end(), instance ); if ( it == pImpl->mInstances.end() ) return; pImpl->mInstances.erase( it ); } // Public accessors. bool WaveBank::IsPrepared() const { if ( pImpl->mPrepared ) return true; if ( !pImpl->mReader.IsPrepared() ) return false; pImpl->mPrepared = true; return true; } bool WaveBank::IsInUse() const { return ( pImpl->mOneShots > 0 ) || !pImpl->mInstances.empty(); } bool WaveBank::IsStreamingBank() const { return pImpl->mReader.IsStreamingBank(); } size_t WaveBank::GetSampleSizeInBytes( int index ) const { if ( index < 0 || uint32_t(index) >= pImpl->mReader.Count() ) return 0; WaveBankReader::Metadata metadata; HRESULT hr = pImpl->mReader.GetMetadata( index, metadata ); ThrowIfFailed( hr ); return metadata.lengthBytes; } size_t WaveBank::GetSampleDuration( int index ) const { if ( index < 0 || uint32_t(index) >= pImpl->mReader.Count() ) return 0; WaveBankReader::Metadata metadata; HRESULT hr = pImpl->mReader.GetMetadata( index, metadata ); ThrowIfFailed( hr ); return metadata.duration; } size_t WaveBank::GetSampleDurationMS( int index ) const { if ( index < 0 || uint32_t(index) >= pImpl->mReader.Count() ) return 0; char buff[64]; auto wfx = reinterpret_cast( buff ); HRESULT hr = pImpl->mReader.GetFormat( index, wfx, 64 ); ThrowIfFailed( hr ); WaveBankReader::Metadata metadata; hr = pImpl->mReader.GetMetadata( index, metadata ); ThrowIfFailed( hr ); return static_cast( ( uint64_t(metadata.duration) * 1000 ) / wfx->nSamplesPerSec ); } _Use_decl_annotations_ const WAVEFORMATEX* WaveBank::GetFormat( int index, WAVEFORMATEX* wfx, size_t maxsize ) const { if ( index < 0 || uint32_t(index) >= pImpl->mReader.Count() ) return nullptr; HRESULT hr = pImpl->mReader.GetFormat( index, wfx, maxsize ); ThrowIfFailed( hr ); return wfx; } _Use_decl_annotations_ int WaveBank::Find( const char* name ) const { return static_cast( pImpl->mReader.Find( name ) ); } #if defined(_XBOX_ONE) || (_WIN32_WINNT < _WIN32_WINNT_WIN8) || (_WIN32_WINNT >= _WIN32_WINNT_WIN10) _Use_decl_annotations_ bool WaveBank::FillSubmitBuffer( int index, XAUDIO2_BUFFER& buffer, XAUDIO2_BUFFER_WMA& wmaBuffer ) const { memset( &buffer, 0, sizeof(buffer) ); memset( &wmaBuffer, 0, sizeof(wmaBuffer) ); HRESULT hr = pImpl->mReader.GetWaveData( index, &buffer.pAudioData, buffer.AudioBytes ); ThrowIfFailed( hr ); WaveBankReader::Metadata metadata; hr = pImpl->mReader.GetMetadata( index, metadata ); ThrowIfFailed( hr ); buffer.LoopBegin = metadata.loopStart; buffer.LoopLength = metadata.loopLength; uint32_t tag; hr = pImpl->mReader.GetSeekTable( index, &wmaBuffer.pDecodedPacketCumulativeBytes, wmaBuffer.PacketCount, tag ); ThrowIfFailed( hr ); return ( tag == WAVE_FORMAT_WMAUDIO2 || tag == WAVE_FORMAT_WMAUDIO3 ); } #else _Use_decl_annotations_ void WaveBank::FillSubmitBuffer( int index, XAUDIO2_BUFFER& buffer ) const { memset( &buffer, 0, sizeof(buffer) ); HRESULT hr = pImpl->mReader.GetWaveData( index, &buffer.pAudioData, buffer.AudioBytes ); ThrowIfFailed( hr ); WaveBankReader::Metadata metadata; hr = pImpl->mReader.GetMetadata( index, metadata ); ThrowIfFailed( hr ); buffer.LoopBegin = metadata.loopStart; buffer.LoopLength = metadata.loopLength; } #endif