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

@ -135,6 +135,6 @@ move "*.nfpack" "$(OutDir)assets\"</Command>
<_delete Include="$(OutDir)**\*" />
</ItemGroup>
<Delete Files="@(_delete)" />
<RemoveDir Directories="$(OUTDIR)" />
<RemoveDir Directories="$(OUTDIR)" />
</Target>
</Project>

View File

@ -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();

View File

@ -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();

View File

@ -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];

View File

@ -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();
}

View File

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

View File

@ -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() {

View File

@ -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();

View File

@ -1,27 +1,50 @@
#pragma once
#include <vector>
#include <thread>
#include <xaudio2.h>
#include <x3daudio.h>
#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<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
#include <xaudio2.h>
#include <x3daudio.h>
#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;
};
}