From c30f159429643b33e478dfb007abbc93fddfab5b Mon Sep 17 00:00:00 2001 From: "Grayson Riffe (Laptop)" Date: Fri, 15 Oct 2021 14:24:08 -0500 Subject: [PATCH] 3D sound now updates in realtime --- Game/Game.vcxproj | 2 +- Game/src/Game.cpp | 3 +- Game/src/MainState.cpp | 7 +- NothinFancy/src/Application.cpp | 8 +- NothinFancy/src/AudioEngine.cpp | 214 +++++++++++++++++++++----- NothinFancy/src/Gamestate.cpp | 1 + NothinFancy/src/NFObject/Sound.cpp | 84 +++------- NothinFancy/src/include/Application.h | 2 +- NothinFancy/src/include/AudioEngine.h | 37 ++++- NothinFancy/src/include/Sound.h | 10 +- 10 files changed, 239 insertions(+), 129 deletions(-) diff --git a/Game/Game.vcxproj b/Game/Game.vcxproj index a7dab41..3a99788 100644 --- a/Game/Game.vcxproj +++ b/Game/Game.vcxproj @@ -135,6 +135,6 @@ move "*.nfpack" "$(OutDir)assets\" <_delete Include="$(OutDir)**\*" /> - + \ No newline at end of file diff --git a/Game/src/Game.cpp b/Game/src/Game.cpp index bd648f7..d966259 100644 --- a/Game/src/Game.cpp +++ b/Game/src/Game.cpp @@ -7,9 +7,10 @@ int main(int argc, char* argv[]) { //app.setWindowIcon(...); // app.setWindowCursor(...); + //Has to be on the heap for some reason MainState* test = new MainState; app.addState(test, "Main State"); - app.addDefaultState("Main State"); + app.setDefaultState("Main State"); app.run(); diff --git a/Game/src/MainState.cpp b/Game/src/MainState.cpp index 07e903a..70d308b 100644 --- a/Game/src/MainState.cpp +++ b/Game/src/MainState.cpp @@ -67,8 +67,11 @@ void MainState::update(double deltaTime) { if (button.isClicked()) app->changeState("Main State"); - if (button.isClicked() || app->isKeyPressed(NFI_SPACE)) - sound.play(); + if (button2.isClicked() || app->isKeyHeld(NFI_SPACE)) + sound.play(true); + + if (app->isKeyPressed(NFI_O)) + sound.stop(); if (app->isKeyPressed(NFI_ESCAPE)) app->quit(); diff --git a/NothinFancy/src/Application.cpp b/NothinFancy/src/Application.cpp index 080767c..14beb93 100644 --- a/NothinFancy/src/Application.cpp +++ b/NothinFancy/src/Application.cpp @@ -57,7 +57,7 @@ namespace nf { Error("State \"" + (std::string)stateName + (std::string)"\" already exists!"); } - void Application::addDefaultState(const std::string& stateName) { + void Application::setDefaultState(const std::string& stateName) { if (!m_defaultStateAdded) { if (m_states.find(stateName) != m_states.end()) { m_defaultState = stateName; @@ -299,7 +299,6 @@ namespace nf { if (m_deltaTime >= m_minFrametime) { lastFrame = std::chrono::steady_clock::now(); m_currentState->update(m_deltaTime); - m_audio->updateSources(); m_currentState->render(*m_renderer); m_renderer->doFrame(m_currentState->getCamera(), m_deltaTime); if (m_stateChange) @@ -318,9 +317,10 @@ namespace nf { } } } - delete m_audio; + m_audio->stopAllSounds(); m_currentState->onExit(); m_currentState->cleanup(); + delete m_audio; delete m_renderer; } @@ -332,7 +332,7 @@ namespace nf { } if (m_renderer->isFadeOutComplete()) { - m_audio->cleanup(); + m_audio->stopAllSounds(); m_currentState->onExit(); m_currentState->cleanup(); m_currentState = m_states[m_nextState]; diff --git a/NothinFancy/src/AudioEngine.cpp b/NothinFancy/src/AudioEngine.cpp index 5f27c80..e4c9cbc 100644 --- a/NothinFancy/src/AudioEngine.cpp +++ b/NothinFancy/src/AudioEngine.cpp @@ -1,66 +1,200 @@ #include "AudioEngine.h" #include "Application.h" +#include "Entity.h" namespace nf { AudioEngine::AudioEngine(Application* app) : m_app(app), m_engine(nullptr), - m_masterVoice(nullptr) + m_masterVoice(nullptr), + m_isActive(false), + m_threadRunning(false), + m_clear(false) { - CoInitializeEx(nullptr, COINIT_MULTITHREADED); - XAudio2Create(&m_engine, XAUDIO2_DEBUG_ENGINE); + HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + if (FAILED(hr)) + Error("Could not initialize COM!"); + hr = XAudio2Create(&m_engine); + if (FAILED(hr)) + Error("Could not initialize the audio engine!"); +#ifdef _DEBUG XAUDIO2_DEBUG_CONFIGURATION debug = { 0 }; debug.TraceMask = XAUDIO2_LOG_ERRORS | XAUDIO2_LOG_WARNINGS; debug.BreakMask = XAUDIO2_LOG_ERRORS; m_engine->SetDebugConfiguration(&debug, 0); - m_engine->CreateMasteringVoice(&m_masterVoice); - DWORD channelMask; - m_masterVoice->GetChannelMask(&channelMask); - X3DAudioInitialize(channelMask, X3DAUDIO_SPEED_OF_SOUND, m_x3d); +#endif + hr = m_engine->CreateMasteringVoice(&m_masterVoice); + if (hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND)) + m_isActive = false; + else if (SUCCEEDED(hr)) + m_isActive = true; + else + Error("Could not initialize the audio engine!"); + m_threadRunning = true; + m_thread = std::thread(&AudioEngine::runAudioThread, this); } - void AudioEngine::updateSources() { - for (unsigned int i = 0; i < m_voices.size(); i++) { - XAUDIO2_VOICE_STATE state; - m_voices[i]->GetState(&state); - if (state.BuffersQueued == 0) { - m_voices[i]->Stop(); - m_voices[i]->FlushSourceBuffers(); - m_voices[i]->DestroyVoice(); - m_voices.erase(m_voices.begin() + i); - i = 0; + bool AudioEngine::isActive() { + if (!m_isActive) { + HRESULT hr = m_engine->CreateMasteringVoice(&m_masterVoice); + if (hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND)) + return false; + else if (hr == S_OK) { + m_isActive = true; + return true; + } + else { + Error("Could not initialize audio!"); + return false; } } + else + return true; } - IXAudio2SourceVoice* AudioEngine::getNewSourceVoice(WAVEFORMATEXTENSIBLE* fmt) { - IXAudio2SourceVoice* s; - HRESULT hr = m_engine->CreateSourceVoice(&s, &fmt->Format, XAUDIO2_VOICE_USEFILTER); - m_voices.push_back(s); - return s; - } - - IXAudio2MasteringVoice* AudioEngine::getMasterVoice() { - return m_masterVoice; - } - - X3DAUDIO_HANDLE* AudioEngine::getX3DAudioInstance() { - return &m_x3d; - } - - void AudioEngine::cleanup() { - for (unsigned int i = 0; i < m_voices.size(); i++) { - m_voices[i]->Stop(); - m_voices[i]->FlushSourceBuffers(); - m_voices[i]->DestroyVoice(); + void AudioEngine::runAudioThread() { +#ifdef _DEBUG + SetThreadDescription(GetCurrentThread(), L"Audio Thread"); +#endif + //Wait to initialize stuff until the master voice is created if it hasn't been already + while (!m_isActive) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); } - m_voices.clear(); + + DWORD cm; + m_masterVoice->GetChannelMask(&cm); + X3DAUDIO_HANDLE x3d; + X3DAudioInitialize(cm, X3DAUDIO_SPEED_OF_SOUND, x3d); + X3DAUDIO_LISTENER listener; + std::memset(&listener, 0, sizeof(X3DAUDIO_LISTENER)); + listener.OrientTop = X3DAUDIO_VECTOR(0.0, 1.0, 0.0); + X3DAUDIO_EMITTER emitter; + std::memset(&emitter, 0, sizeof(X3DAUDIO_EMITTER)); + emitter.OrientTop = X3DAUDIO_VECTOR(0.0, 1.0, 0.0); + emitter.OrientFront = X3DAUDIO_VECTOR(0.0, 0.0, 1.0); + emitter.CurveDistanceScaler = 1.0f; + X3DAUDIO_DSP_SETTINGS x3dSettings; + std::memset(&x3dSettings, 0, sizeof(X3DAUDIO_DSP_SETTINGS)); + float matrix[20] = { 0 }; + x3dSettings.pMatrixCoefficients = matrix; + float az[20] = { 0 }; + emitter.pChannelAzimuths = az; + XAUDIO2_FILTER_PARAMETERS filter = { LowPassFilter, 1.0, 1.0 }; + XAUDIO2_VOICE_STATE state; + Vec3 temp; + + while (m_threadRunning) { + if (m_isActive && Application::getApp()->getCurrentState()->isRunning()) { + //Update listener position + temp = Application::getApp()->getCurrentState()->getCamera()->getPosition(); + listener.Position = X3DAUDIO_VECTOR((float)temp.x, (float)temp.y, (float)-temp.z); + temp = Application::getApp()->getCurrentState()->getCamera()->getRotation(); + listener.OrientFront = X3DAUDIO_VECTOR((float)temp.x, 0.0f, (float)-temp.z); + + //Stop all sounds if requested + if (m_clear) + clearSounds(); + + //Update sounds + for (SoundData& curr : m_sounds) { + //Skip finished sounds + if (curr.finished) continue; + //Start sound if not started yet + if (curr.start) { + curr.start = false; + IXAudio2SourceVoice* source; + HRESULT hr = m_engine->CreateSourceVoice(&source, (WAVEFORMATEX*)curr.format, XAUDIO2_VOICE_USEFILTER); + if (hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND)) { + m_isActive = false; + m_clear = true; + break; + } + else if (!SUCCEEDED(hr)) + Error("Could not play sound!"); + curr.voice = source; + curr.voice->SubmitSourceBuffer(curr.buffer); + curr.voice->SetVolume(curr.volume); + curr.voice->Start(); + } + //Finish sound + curr.voice->GetState(&state); + if (state.BuffersQueued == 0) { + curr.finished = true; + continue; + } + + //Update playing sound + if (curr.playAtPosition) + temp = curr.position; + if (curr.trackToEntity) + temp = curr.trackedEntity->getPosition(); + + if (curr.playAtPosition || curr.trackToEntity) { + int ch = curr.format->Format.nChannels; + emitter.ChannelCount = ch; + x3dSettings.SrcChannelCount = ch; + x3dSettings.DstChannelCount = ch; + emitter.Position = X3DAUDIO_VECTOR((float)temp.x, (float)temp.y, (float)-temp.z); + X3DAudioCalculate(x3d, &listener, &emitter, X3DAUDIO_CALCULATE_MATRIX | X3DAUDIO_CALCULATE_DOPPLER | X3DAUDIO_CALCULATE_LPF_DIRECT | X3DAUDIO_CALCULATE_REVERB, &x3dSettings); + float temp2 = matrix[1]; + matrix[1] = matrix[2]; + matrix[2] = temp2; + curr.voice->SetOutputMatrix(m_masterVoice, ch, ch, matrix); + curr.voice->SetFrequencyRatio(x3dSettings.DopplerFactor); + filter.Frequency = 2.0f * std::sinf(X3DAUDIO_PI / 6.0f * x3dSettings.LPFDirectCoefficient); + curr.voice->SetFilterParameters(&filter); + } + } + + //Delete all finished sounds from the list + for (size_t i = 0; i < m_sounds.size(); i++) { + if (m_sounds[i].finished) { + m_sounds[i].voice->Stop(); + m_sounds[i].voice->FlushSourceBuffers(); + m_sounds[i].voice->DestroyVoice(); + m_sounds.erase(m_sounds.begin() + i); + } + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + + //Cleanup + clearSounds(); + m_masterVoice->DestroyVoice(); + } + + void AudioEngine::addSound(SoundData& data) { + m_sounds.push_back(data); + } + + void AudioEngine::stopSound(const XAUDIO2_BUFFER* buffer) { + //Maybe should move to audio thread somehow? + for (SoundData& curr : m_sounds) { + if (std::memcmp(curr.buffer, buffer, sizeof(XAUDIO2_BUFFER)) == 0) + curr.finished = true; + } + } + + void AudioEngine::stopAllSounds() { + m_clear = true; + } + + void AudioEngine::clearSounds() { + m_clear = false; + for (SoundData& curr : m_sounds) { + if (curr.start) continue; + curr.voice->Stop(); + curr.voice->FlushSourceBuffers(); + curr.voice->DestroyVoice(); + } + m_sounds.clear(); } AudioEngine::~AudioEngine() { - cleanup(); - m_masterVoice->DestroyVoice(); + m_threadRunning = false; + m_thread.join(); m_engine->Release(); CoUninitialize(); } diff --git a/NothinFancy/src/Gamestate.cpp b/NothinFancy/src/Gamestate.cpp index ac45254..c4f8556 100644 --- a/NothinFancy/src/Gamestate.cpp +++ b/NothinFancy/src/Gamestate.cpp @@ -65,5 +65,6 @@ namespace nf { delete camera; app = nullptr; + m_running = false; } } \ No newline at end of file diff --git a/NothinFancy/src/NFObject/Sound.cpp b/NothinFancy/src/NFObject/Sound.cpp index cfd7a42..2e5f6e8 100644 --- a/NothinFancy/src/NFObject/Sound.cpp +++ b/NothinFancy/src/NFObject/Sound.cpp @@ -3,19 +3,17 @@ #include "Application.h" #include "Assets.h" #include "Entity.h" -#include "Utility.h" namespace nf { Sound::Sound() : m_constructed(false), - m_dataSize(0), m_volume(1.0f), m_usePos(false), + m_useEntity(false), m_format({ 0 }), + m_xBuffer({ 0 }), m_buffer(nullptr), - m_currentVoice(nullptr), - m_targetEntity(nullptr), - m_soundPos(0.0) + m_targetEntity(nullptr) { } @@ -38,15 +36,15 @@ namespace nf { size_t dataPos; if ((dataPos = data.find("data")) == std::string::npos) Error("Sound asset not of correct m_format!"); - m_dataSize = *(unsigned int*)&data[dataPos + 4]; - m_buffer = new unsigned char[m_dataSize]; - std::memcpy(m_buffer, &data[dataPos + 8], m_dataSize); + unsigned int dataSize = *(unsigned int*)&data[dataPos + 4]; + m_buffer = new unsigned char[dataSize]; + std::memcpy(m_buffer, &data[dataPos + 8], dataSize); + m_xBuffer.pAudioData = m_buffer; + m_xBuffer.AudioBytes = dataSize; + m_xBuffer.Flags = XAUDIO2_END_OF_STREAM; - m_emitter = { 0 }; - m_emitter.ChannelCount = 2; - m_emitter.CurveDistanceScaler = 1.0; - - Application::getApp()->getCurrentState()->m_nfObjects.push_back(this); + if (!Application::getApp()->getCurrentState()->isRunning()) + Application::getApp()->getCurrentState()->m_nfObjects.push_back(this); } void Sound::setVolume(double volume) { @@ -55,72 +53,28 @@ namespace nf { void Sound::setEntity(Entity& entity) { m_targetEntity = &entity; + m_useEntity = true; m_usePos = false; } void Sound::setPosition(const Vec3& position) { m_soundPos = position; m_usePos = true; + m_useEntity = false; } void Sound::play(bool loop) { - m_currentVoice = Application::getApp()->getAudioEngine()->getNewSourceVoice(&m_format); - m_currentVoice->SetVolume(m_volume); + if (!Application::getApp()->getAudioEngine()->isActive()) return; - XAUDIO2_BUFFER xBuffer = { 0 }; - xBuffer.pAudioData = m_buffer; - xBuffer.AudioBytes = m_dataSize; if (loop) - xBuffer.LoopCount = XAUDIO2_LOOP_INFINITE; + m_xBuffer.LoopCount = XAUDIO2_LOOP_INFINITE; - if (m_usePos || m_targetEntity) { - if (m_usePos) - m_emitter.Position = X3DAUDIO_VECTOR((float)m_soundPos.x, (float)m_soundPos.y, (float)-m_soundPos.z); - else if (m_targetEntity) { - Vec3 temp = m_targetEntity->getPosition(); - m_emitter.Position = X3DAUDIO_VECTOR((float)temp.x, (float)temp.y, (float)-temp.z); - } - m_emitter.OrientFront = X3DAUDIO_VECTOR(0.0, 0.0, 1.0); - m_emitter.OrientTop = X3DAUDIO_VECTOR(0.0, 1.0, 0.0); - float az[2] = { 0 }; - m_emitter.pChannelAzimuths = az; - Vec3 temp = Application::getApp()->getCurrentState()->getCamera()->getPosition(); - m_listener.Position = X3DAUDIO_VECTOR((float)temp.x, (float)temp.y, (float)-temp.z); - temp = Application::getApp()->getCurrentState()->getCamera()->getRotation(); - m_listener.OrientFront = X3DAUDIO_VECTOR((float)temp.x, 0.0f, (float)-temp.z); - m_listener.OrientTop = X3DAUDIO_VECTOR(0.0, 1.0, 0.0); - X3DAUDIO_DSP_SETTINGS settings = { 0 }; - settings.SrcChannelCount = 2; - settings.DstChannelCount = 2; - float matrix[4] = { 0 }; - settings.pMatrixCoefficients = matrix; - - IXAudio2MasteringVoice* master = Application::getApp()->getAudioEngine()->getMasterVoice(); - X3DAUDIO_HANDLE* instance = Application::getApp()->getAudioEngine()->getX3DAudioInstance(); - X3DAudioCalculate(*instance, &m_listener, &m_emitter, X3DAUDIO_CALCULATE_MATRIX | X3DAUDIO_CALCULATE_DOPPLER | X3DAUDIO_CALCULATE_LPF_DIRECT | X3DAUDIO_CALCULATE_REVERB, &settings); - float temp2 = settings.pMatrixCoefficients[1]; - settings.pMatrixCoefficients[1] = settings.pMatrixCoefficients[2]; - settings.pMatrixCoefficients[2] = temp2; - m_currentVoice->SetOutputMatrix(master, 2, 2, settings.pMatrixCoefficients); - m_currentVoice->SetFrequencyRatio(settings.DopplerFactor); - XAUDIO2_FILTER_PARAMETERS lpf = { LowPassFilter, 2.0f * std::sinf(X3DAUDIO_PI / 6.0f * settings.LPFDirectCoefficient), 1.0f }; - m_currentVoice->SetFilterParameters(&lpf); - } - - m_currentVoice->SubmitSourceBuffer(&xBuffer); - m_currentVoice->Start(); + SoundData sd = { &m_format, &m_xBuffer, nullptr, true, m_volume, m_useEntity, m_targetEntity, m_usePos, m_soundPos }; + Application::getApp()->getAudioEngine()->addSound(sd); } void Sound::stop() { - if (m_currentVoice) { - XAUDIO2_VOICE_STATE state; - m_currentVoice->GetState(&state); - if (state.BuffersQueued > 0) { - m_currentVoice->Stop(); - m_currentVoice->FlushSourceBuffers(); - m_currentVoice = nullptr; - } - } + Application::getApp()->getAudioEngine()->stopSound(&m_xBuffer); } void Sound::destroy() { @@ -129,10 +83,8 @@ namespace nf { m_buffer = nullptr; } m_constructed = false; - m_dataSize = 0; m_volume = 1.0f; m_format = { 0 }; - m_currentVoice = nullptr; } Sound::~Sound() { diff --git a/NothinFancy/src/include/Application.h b/NothinFancy/src/include/Application.h index cc6e7d3..3fa5200 100644 --- a/NothinFancy/src/include/Application.h +++ b/NothinFancy/src/include/Application.h @@ -21,7 +21,7 @@ namespace nf { void setWindowCursor(HCURSOR hCursor); AudioEngine* getAudioEngine() const; void addState(Gamestate* state, const std::string& stateName); - void addDefaultState(const std::string& stateName); + void setDefaultState(const std::string& stateName); const std::string& getDefaultState(); void run(); bool isCustomWindowIcon(); diff --git a/NothinFancy/src/include/AudioEngine.h b/NothinFancy/src/include/AudioEngine.h index 6eada2e..a273dec 100644 --- a/NothinFancy/src/include/AudioEngine.h +++ b/NothinFancy/src/include/AudioEngine.h @@ -1,27 +1,50 @@ #pragma once #include +#include #include #include +#include "Utility.h" + namespace nf { + class Entity; + + struct SoundData { + WAVEFORMATEXTENSIBLE* format; + XAUDIO2_BUFFER* buffer; + IXAudio2SourceVoice* voice; + bool start; + float volume; + bool trackToEntity; + Entity* trackedEntity; + bool playAtPosition; + Vec3 position; + bool finished = false; + }; + class Application; class AudioEngine { public: AudioEngine(Application* app); - void updateSources(); - IXAudio2SourceVoice* getNewSourceVoice(WAVEFORMATEXTENSIBLE* fmt); - IXAudio2MasteringVoice* getMasterVoice(); - X3DAUDIO_HANDLE* getX3DAudioInstance(); + bool isActive(); + void runAudioThread(); + void addSound(SoundData& data); + void stopSound(const XAUDIO2_BUFFER* buffer); - void cleanup(); + void stopAllSounds(); ~AudioEngine(); private: + void clearSounds(); + Application* m_app; IXAudio2* m_engine; - X3DAUDIO_HANDLE m_x3d; IXAudio2MasteringVoice* m_masterVoice; - std::vector m_voices; + bool m_isActive; + bool m_threadRunning; + std::thread m_thread; + std::vector m_sounds; + bool m_clear; }; } \ No newline at end of file diff --git a/NothinFancy/src/include/Sound.h b/NothinFancy/src/include/Sound.h index 2463f68..77dd030 100644 --- a/NothinFancy/src/include/Sound.h +++ b/NothinFancy/src/include/Sound.h @@ -1,8 +1,6 @@ #pragma once -#include -#include - #include "NFObject.h" +#include "AudioEngine.h" #include "Utility.h" namespace nf { @@ -24,15 +22,13 @@ namespace nf { ~Sound(); private: bool m_constructed; - unsigned int m_dataSize; float m_volume; bool m_usePos; + bool m_useEntity; WAVEFORMATEXTENSIBLE m_format; + XAUDIO2_BUFFER m_xBuffer; unsigned char* m_buffer; - IXAudio2SourceVoice* m_currentVoice; Entity* m_targetEntity; Vec3 m_soundPos; - X3DAUDIO_EMITTER m_emitter; - X3DAUDIO_LISTENER m_listener; }; } \ No newline at end of file