3D sound now updates in realtime

This commit is contained in:
Grayson Riffe (Laptop) 2021-10-15 14:24:08 -05:00
parent 98d093c285
commit c30f159429
10 changed files with 239 additions and 129 deletions

View File

@ -7,9 +7,10 @@ int main(int argc, char* argv[]) {
//app.setWindowIcon(...); //app.setWindowIcon(...);
// app.setWindowCursor(...); // app.setWindowCursor(...);
//Has to be on the heap for some reason
MainState* test = new MainState; MainState* test = new MainState;
app.addState(test, "Main State"); app.addState(test, "Main State");
app.addDefaultState("Main State"); app.setDefaultState("Main State");
app.run(); app.run();

View File

@ -67,8 +67,11 @@ void MainState::update(double deltaTime) {
if (button.isClicked()) if (button.isClicked())
app->changeState("Main State"); app->changeState("Main State");
if (button.isClicked() || app->isKeyPressed(NFI_SPACE)) if (button2.isClicked() || app->isKeyHeld(NFI_SPACE))
sound.play(); sound.play(true);
if (app->isKeyPressed(NFI_O))
sound.stop();
if (app->isKeyPressed(NFI_ESCAPE)) if (app->isKeyPressed(NFI_ESCAPE))
app->quit(); app->quit();

View File

@ -57,7 +57,7 @@ namespace nf {
Error("State \"" + (std::string)stateName + (std::string)"\" already exists!"); 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_defaultStateAdded) {
if (m_states.find(stateName) != m_states.end()) { if (m_states.find(stateName) != m_states.end()) {
m_defaultState = stateName; m_defaultState = stateName;
@ -299,7 +299,6 @@ namespace nf {
if (m_deltaTime >= m_minFrametime) { if (m_deltaTime >= m_minFrametime) {
lastFrame = std::chrono::steady_clock::now(); lastFrame = std::chrono::steady_clock::now();
m_currentState->update(m_deltaTime); m_currentState->update(m_deltaTime);
m_audio->updateSources();
m_currentState->render(*m_renderer); m_currentState->render(*m_renderer);
m_renderer->doFrame(m_currentState->getCamera(), m_deltaTime); m_renderer->doFrame(m_currentState->getCamera(), m_deltaTime);
if (m_stateChange) if (m_stateChange)
@ -318,9 +317,10 @@ namespace nf {
} }
} }
} }
delete m_audio; m_audio->stopAllSounds();
m_currentState->onExit(); m_currentState->onExit();
m_currentState->cleanup(); m_currentState->cleanup();
delete m_audio;
delete m_renderer; delete m_renderer;
} }
@ -332,7 +332,7 @@ namespace nf {
} }
if (m_renderer->isFadeOutComplete()) { if (m_renderer->isFadeOutComplete()) {
m_audio->cleanup(); m_audio->stopAllSounds();
m_currentState->onExit(); m_currentState->onExit();
m_currentState->cleanup(); m_currentState->cleanup();
m_currentState = m_states[m_nextState]; m_currentState = m_states[m_nextState];

View File

@ -1,66 +1,200 @@
#include "AudioEngine.h" #include "AudioEngine.h"
#include "Application.h" #include "Application.h"
#include "Entity.h"
namespace nf { namespace nf {
AudioEngine::AudioEngine(Application* app) : AudioEngine::AudioEngine(Application* app) :
m_app(app), m_app(app),
m_engine(nullptr), m_engine(nullptr),
m_masterVoice(nullptr) m_masterVoice(nullptr),
m_isActive(false),
m_threadRunning(false),
m_clear(false)
{ {
CoInitializeEx(nullptr, COINIT_MULTITHREADED); HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
XAudio2Create(&m_engine, XAUDIO2_DEBUG_ENGINE); 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 }; XAUDIO2_DEBUG_CONFIGURATION debug = { 0 };
debug.TraceMask = XAUDIO2_LOG_ERRORS | XAUDIO2_LOG_WARNINGS; debug.TraceMask = XAUDIO2_LOG_ERRORS | XAUDIO2_LOG_WARNINGS;
debug.BreakMask = XAUDIO2_LOG_ERRORS; debug.BreakMask = XAUDIO2_LOG_ERRORS;
m_engine->SetDebugConfiguration(&debug, 0); m_engine->SetDebugConfiguration(&debug, 0);
m_engine->CreateMasteringVoice(&m_masterVoice); #endif
DWORD channelMask; hr = m_engine->CreateMasteringVoice(&m_masterVoice);
m_masterVoice->GetChannelMask(&channelMask); if (hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND))
X3DAudioInitialize(channelMask, X3DAUDIO_SPEED_OF_SOUND, m_x3d); 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() { bool AudioEngine::isActive() {
for (unsigned int i = 0; i < m_voices.size(); i++) { 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;
}
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));
}
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; XAUDIO2_VOICE_STATE state;
m_voices[i]->GetState(&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) { if (state.BuffersQueued == 0) {
m_voices[i]->Stop(); curr.finished = true;
m_voices[i]->FlushSourceBuffers(); continue;
m_voices[i]->DestroyVoice();
m_voices.erase(m_voices.begin() + i);
i = 0;
} }
//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);
} }
} }
IXAudio2SourceVoice* AudioEngine::getNewSourceVoice(WAVEFORMATEXTENSIBLE* fmt) { //Delete all finished sounds from the list
IXAudio2SourceVoice* s; for (size_t i = 0; i < m_sounds.size(); i++) {
HRESULT hr = m_engine->CreateSourceVoice(&s, &fmt->Format, XAUDIO2_VOICE_USEFILTER); if (m_sounds[i].finished) {
m_voices.push_back(s); m_sounds[i].voice->Stop();
return s; 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));
} }
IXAudio2MasteringVoice* AudioEngine::getMasterVoice() { //Cleanup
return m_masterVoice; clearSounds();
m_masterVoice->DestroyVoice();
} }
X3DAUDIO_HANDLE* AudioEngine::getX3DAudioInstance() { void AudioEngine::addSound(SoundData& data) {
return &m_x3d; m_sounds.push_back(data);
} }
void AudioEngine::cleanup() { void AudioEngine::stopSound(const XAUDIO2_BUFFER* buffer) {
for (unsigned int i = 0; i < m_voices.size(); i++) { //Maybe should move to audio thread somehow?
m_voices[i]->Stop(); for (SoundData& curr : m_sounds) {
m_voices[i]->FlushSourceBuffers(); if (std::memcmp(curr.buffer, buffer, sizeof(XAUDIO2_BUFFER)) == 0)
m_voices[i]->DestroyVoice(); curr.finished = true;
} }
m_voices.clear(); }
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() { AudioEngine::~AudioEngine() {
cleanup(); m_threadRunning = false;
m_masterVoice->DestroyVoice(); m_thread.join();
m_engine->Release(); m_engine->Release();
CoUninitialize(); CoUninitialize();
} }

View File

@ -65,5 +65,6 @@ namespace nf {
delete camera; delete camera;
app = nullptr; app = nullptr;
m_running = false;
} }
} }

View File

@ -3,19 +3,17 @@
#include "Application.h" #include "Application.h"
#include "Assets.h" #include "Assets.h"
#include "Entity.h" #include "Entity.h"
#include "Utility.h"
namespace nf { namespace nf {
Sound::Sound() : Sound::Sound() :
m_constructed(false), m_constructed(false),
m_dataSize(0),
m_volume(1.0f), m_volume(1.0f),
m_usePos(false), m_usePos(false),
m_useEntity(false),
m_format({ 0 }), m_format({ 0 }),
m_xBuffer({ 0 }),
m_buffer(nullptr), m_buffer(nullptr),
m_currentVoice(nullptr), m_targetEntity(nullptr)
m_targetEntity(nullptr),
m_soundPos(0.0)
{ {
} }
@ -38,14 +36,14 @@ namespace nf {
size_t dataPos; size_t dataPos;
if ((dataPos = data.find("data")) == std::string::npos) if ((dataPos = data.find("data")) == std::string::npos)
Error("Sound asset not of correct m_format!"); Error("Sound asset not of correct m_format!");
m_dataSize = *(unsigned int*)&data[dataPos + 4]; unsigned int dataSize = *(unsigned int*)&data[dataPos + 4];
m_buffer = new unsigned char[m_dataSize]; m_buffer = new unsigned char[dataSize];
std::memcpy(m_buffer, &data[dataPos + 8], m_dataSize); std::memcpy(m_buffer, &data[dataPos + 8], dataSize);
m_xBuffer.pAudioData = m_buffer;
m_emitter = { 0 }; m_xBuffer.AudioBytes = dataSize;
m_emitter.ChannelCount = 2; m_xBuffer.Flags = XAUDIO2_END_OF_STREAM;
m_emitter.CurveDistanceScaler = 1.0;
if (!Application::getApp()->getCurrentState()->isRunning())
Application::getApp()->getCurrentState()->m_nfObjects.push_back(this); Application::getApp()->getCurrentState()->m_nfObjects.push_back(this);
} }
@ -55,72 +53,28 @@ namespace nf {
void Sound::setEntity(Entity& entity) { void Sound::setEntity(Entity& entity) {
m_targetEntity = &entity; m_targetEntity = &entity;
m_useEntity = true;
m_usePos = false; m_usePos = false;
} }
void Sound::setPosition(const Vec3& position) { void Sound::setPosition(const Vec3& position) {
m_soundPos = position; m_soundPos = position;
m_usePos = true; m_usePos = true;
m_useEntity = false;
} }
void Sound::play(bool loop) { void Sound::play(bool loop) {
m_currentVoice = Application::getApp()->getAudioEngine()->getNewSourceVoice(&m_format); if (!Application::getApp()->getAudioEngine()->isActive()) return;
m_currentVoice->SetVolume(m_volume);
XAUDIO2_BUFFER xBuffer = { 0 };
xBuffer.pAudioData = m_buffer;
xBuffer.AudioBytes = m_dataSize;
if (loop) if (loop)
xBuffer.LoopCount = XAUDIO2_LOOP_INFINITE; m_xBuffer.LoopCount = XAUDIO2_LOOP_INFINITE;
if (m_usePos || m_targetEntity) { SoundData sd = { &m_format, &m_xBuffer, nullptr, true, m_volume, m_useEntity, m_targetEntity, m_usePos, m_soundPos };
if (m_usePos) Application::getApp()->getAudioEngine()->addSound(sd);
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();
} }
void Sound::stop() { void Sound::stop() {
if (m_currentVoice) { Application::getApp()->getAudioEngine()->stopSound(&m_xBuffer);
XAUDIO2_VOICE_STATE state;
m_currentVoice->GetState(&state);
if (state.BuffersQueued > 0) {
m_currentVoice->Stop();
m_currentVoice->FlushSourceBuffers();
m_currentVoice = nullptr;
}
}
} }
void Sound::destroy() { void Sound::destroy() {
@ -129,10 +83,8 @@ namespace nf {
m_buffer = nullptr; m_buffer = nullptr;
} }
m_constructed = false; m_constructed = false;
m_dataSize = 0;
m_volume = 1.0f; m_volume = 1.0f;
m_format = { 0 }; m_format = { 0 };
m_currentVoice = nullptr;
} }
Sound::~Sound() { Sound::~Sound() {

View File

@ -21,7 +21,7 @@ namespace nf {
void setWindowCursor(HCURSOR hCursor); void setWindowCursor(HCURSOR hCursor);
AudioEngine* getAudioEngine() const; AudioEngine* getAudioEngine() const;
void addState(Gamestate* state, const std::string& stateName); void addState(Gamestate* state, const std::string& stateName);
void addDefaultState(const std::string& stateName); void setDefaultState(const std::string& stateName);
const std::string& getDefaultState(); const std::string& getDefaultState();
void run(); void run();
bool isCustomWindowIcon(); bool isCustomWindowIcon();

View File

@ -1,27 +1,50 @@
#pragma once #pragma once
#include <vector> #include <vector>
#include <thread>
#include <xaudio2.h> #include <xaudio2.h>
#include <x3daudio.h> #include <x3daudio.h>
#include "Utility.h"
namespace nf { 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 Application;
class AudioEngine { class AudioEngine {
public: public:
AudioEngine(Application* app); AudioEngine(Application* app);
void updateSources(); bool isActive();
IXAudio2SourceVoice* getNewSourceVoice(WAVEFORMATEXTENSIBLE* fmt); void runAudioThread();
IXAudio2MasteringVoice* getMasterVoice(); void addSound(SoundData& data);
X3DAUDIO_HANDLE* getX3DAudioInstance(); void stopSound(const XAUDIO2_BUFFER* buffer);
void cleanup(); void stopAllSounds();
~AudioEngine(); ~AudioEngine();
private: private:
void clearSounds();
Application* m_app; Application* m_app;
IXAudio2* m_engine; IXAudio2* m_engine;
X3DAUDIO_HANDLE m_x3d;
IXAudio2MasteringVoice* m_masterVoice; IXAudio2MasteringVoice* m_masterVoice;
std::vector<IXAudio2SourceVoice*> m_voices; bool m_isActive;
bool m_threadRunning;
std::thread m_thread;
std::vector<SoundData> m_sounds;
bool m_clear;
}; };
} }

View File

@ -1,8 +1,6 @@
#pragma once #pragma once
#include <xaudio2.h>
#include <x3daudio.h>
#include "NFObject.h" #include "NFObject.h"
#include "AudioEngine.h"
#include "Utility.h" #include "Utility.h"
namespace nf { namespace nf {
@ -24,15 +22,13 @@ namespace nf {
~Sound(); ~Sound();
private: private:
bool m_constructed; bool m_constructed;
unsigned int m_dataSize;
float m_volume; float m_volume;
bool m_usePos; bool m_usePos;
bool m_useEntity;
WAVEFORMATEXTENSIBLE m_format; WAVEFORMATEXTENSIBLE m_format;
XAUDIO2_BUFFER m_xBuffer;
unsigned char* m_buffer; unsigned char* m_buffer;
IXAudio2SourceVoice* m_currentVoice;
Entity* m_targetEntity; Entity* m_targetEntity;
Vec3 m_soundPos; Vec3 m_soundPos;
X3DAUDIO_EMITTER m_emitter;
X3DAUDIO_LISTENER m_listener;
}; };
} }