#include "nf/AudioEngine.h" #include "nf/Application.h" #include "nf/Entity.h" namespace nf { AudioEngine::AudioEngine(Application* app) : m_app(app), m_engine(nullptr), m_masterVoice(nullptr), m_isActive(false), m_threadRunning(false), m_clear(false) { HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if (FAILED(hr)) NFError("Could not initialize COM!"); hr = XAudio2Create(&m_engine); if (FAILED(hr)) NFError("Could not initialize 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); #endif hr = m_engine->CreateMasteringVoice(&m_masterVoice); if (hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND)) { m_isActive = false; NFLog("Audio engine not initialized since no audio devices found"); } else if (SUCCEEDED(hr)) { m_isActive = true; NFLog("Initialized audio engine"); } else NFError("Could not initialize audio engine!"); m_threadRunning = true; m_thread = std::thread(&AudioEngine::runAudioThread, this); } 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; NFLog("Initialized audio engine"); return true; } else { NFError("Could not initialize audio engine!"); 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_threadRunning && !m_isActive) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } if (!m_isActive) return; 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 }; Vec3 temp; XAUDIO2_VOICE_STATE state; XAUDIO2_VOICE_DETAILS details; m_masterVoice->GetVoiceDetails(&details); unsigned int outChannels = details.InputChannels; x3dSettings.DstChannelCount = outChannels; while (m_threadRunning) { if (m_isActive && Application::getApp()->getCurrentState() && Application::getApp()->getCurrentState()->isRunning()) { //Update listener position temp = Application::getApp()->getCurrentState()->getCamera()->getPosition(); listener.Position = X3DAUDIO_VECTOR(temp.x, temp.y, -temp.z); temp = Application::getApp()->getCurrentState()->getCamera()->getRotation(); listener.OrientFront = X3DAUDIO_VECTOR(temp.x, 0.0f, -temp.z); //Stop all sounds if requested if (m_clear) stopAllSounds(); //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)) NFError("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; emitter.Position = X3DAUDIO_VECTOR(temp.x, temp.y, -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, outChannels, 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) { 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 stopAllSounds(); 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 = false; for (SoundData& curr : m_sounds) { if (curr.start) continue; curr.voice->Stop(); curr.voice->FlushSourceBuffers(); curr.voice->DestroyVoice(); } m_sounds.clear(); } AudioEngine::~AudioEngine() { stopAllSounds(); m_threadRunning = false; m_thread.join(); m_engine->Release(); CoUninitialize(); } }