diff --git a/CMakeLists.txt b/CMakeLists.txt index 88d740e..a120a7d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,7 +56,9 @@ set(SOURCES src/SoundSources/BaseSoundSource.cpp src/SoundSources/StaticSoundSource.cpp src/SoundSources/StreamingSoundSource.cpp -) + src/SteamAudio/Context.cpp + src/SteamAudioLib.cpp + "src/SteamAudio/Simulator.cpp" "src/SteamAudio/Scene.cpp" "src/SteamAudio/StaticMesh.cpp" "src/SteamAudio/Source.cpp") add_library(MetaAudio SHARED ${SOURCES}) diff --git a/include/AudioEngine.hpp b/include/AudioEngine.hpp index 1bf441c..32d6e8f 100644 --- a/include/AudioEngine.hpp +++ b/include/AudioEngine.hpp @@ -9,6 +9,7 @@ #include "Utilities/AudioCache.hpp" #include "Loaders/SoundLoader.hpp" #include "Utilities/ChannelManager.hpp" +#include "SteamAudio/Context.hpp" namespace MetaAudio { @@ -59,9 +60,7 @@ namespace MetaAudio void OpenAL_Shutdown(); // SteamAudio - IPLhandle sa_context = nullptr; - IPLSimulationSettings sa_simulationSettings{}; - IPLhandle sa_environment = nullptr; + SteamAudio::Context sa_context = nullptr; void SteamAudio_Init(); void SteamAudio_Shutdown(); diff --git a/include/Loaders/SteamAudioMapMeshLoader.hpp b/include/Loaders/SteamAudioMapMeshLoader.hpp index 7f8d4fa..bf614ef 100644 --- a/include/Loaders/SteamAudioMapMeshLoader.hpp +++ b/include/Loaders/SteamAudioMapMeshLoader.hpp @@ -2,84 +2,58 @@ #include #include "alure2.h" -#include "dynamic_steamaudio.h" +#include "SteamAudio/Context.hpp" +#include "SteamAudio/Scene.hpp" +#include "SteamAudio/Simulator.hpp" namespace MetaAudio { - class SteamAudioMapMeshLoader final - { - private: - class ProcessedMap final - { - private: - std::string mapName; - IPLhandle environment; - IPLhandle scene; - IPLhandle static_mesh; - - public: - ProcessedMap(const std::string& mapName, IPLhandle env, IPLhandle scene, IPLhandle mesh) - : environment(env), scene(scene), static_mesh(mesh), mapName(mapName) - { - } - - ~ProcessedMap() - { - if (environment != nullptr) - { - gSteamAudio.iplDestroyEnvironment(&environment); - } - - if (scene != nullptr) - { - gSteamAudio.iplDestroyScene(&scene); - } - - if (static_mesh != nullptr) - { - gSteamAudio.iplDestroyStaticMesh(&static_mesh); - } - } - - // delete copy - ProcessedMap(const ProcessedMap& other) = delete; - ProcessedMap& ProcessedMap::operator=(const ProcessedMap& other) = delete; - - // allow move - ProcessedMap(ProcessedMap&& other) = default; - ProcessedMap& operator=(ProcessedMap&& other) = default; - - const std::string& Name() - { - return mapName; - } - - IPLhandle Env() - { - return environment; - } - }; - - IPLSimulationSettings sa_simul_settings; - IPLhandle sa_context; - - std::unique_ptr current_map; - - alure::Vector3 Normalize(const alure::Vector3& vector); - float DotProduct(const alure::Vector3& left, const alure::Vector3& right); - - // Transmission details: - // SteamAudio returns the transmission property of the material that was hit, not how much was transmitted - // We should calculate ourselves how much is actually transmitted. The unit used in MetaAudio is actually - // the attenuation `dB/m`, not how much is transmitted per meter. - std::array materials{ {0.10f, 0.20f, 0.30f, 0.05f, 2.0f, 4.0f, (1.0f / 0.15f)} }; - public: - SteamAudioMapMeshLoader(IPLhandle sa_context, IPLSimulationSettings simulSettings); - - // Checks if map is current , if not update it - void update(); - - // get current scene data as an IPLhandle - IPLhandle CurrentEnvironment(); - }; + class SteamAudioMapMeshLoader final + { + private: + class ProcessedMap final + { + private: + SteamAudio::Scene scene = nullptr; + SteamAudio::StaticMesh static_mesh = nullptr; + SteamAudio::Simulator simulator = nullptr; + std::string mapName; + + public: + ProcessedMap(const std::string& mapName, SteamAudio::Scene scene, SteamAudio::StaticMesh mesh, SteamAudio::Simulator simulator) + : scene(scene), static_mesh(mesh), simulator(simulator), mapName(mapName) + { + } + + const std::string& Name() + { + return mapName; + } + + SteamAudio::Simulator Simulator() { + return simulator; + } + }; + + IPLSimulationSettings sa_simul_settings; + SteamAudio::Context sa_context; + + std::unique_ptr current_map = nullptr; + + alure::Vector3 Normalize(const alure::Vector3& vector); + float DotProduct(const alure::Vector3& left, const alure::Vector3& right); + + // Transmission details: + // SteamAudio returns the transmission property of the material that was hit, not how much was transmitted + // We should calculate ourselves how much is actually transmitted. The unit used in MetaAudio is actually + // the attenuation `dB/m`, not how much is transmitted per meter. + std::array materials{ {0.10f,0.20f,0.30f,0.05f,0.100f,0.050f,0.030f} }; + public: + SteamAudioMapMeshLoader(SteamAudio::Context sa_context, IPLSimulationSettings simulSettings); + + // Checks if map is current, if not update it + void update(); + + SteamAudio::Simulator CurrentSimulator(); + }; } \ No newline at end of file diff --git a/include/SteamAudio/Context.hpp b/include/SteamAudio/Context.hpp new file mode 100644 index 0000000..8c0da8d --- /dev/null +++ b/include/SteamAudio/Context.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "SteamAudioLib.h" +#include "ObjectPtr.hpp" +#include "Simulator.hpp" +#include "Scene.hpp" + +namespace MetaAudio { + namespace SteamAudio { + class Context final : public ObjectPtr { + public: + static std::variant Create(IPLContextSettings& settings); + constexpr Context(nullptr_t) noexcept : ObjectPtr(nullptr) {} + Context(IPLContext ptr) : ObjectPtr(ptr) {}; + + std::variant CreateSimulator(IPLSimulationSettings& simulationSettings); + std::variant CreateScene(IPLSceneSettings& sceneSettings); + + protected: + // Inherited via ObjectPtr + void retain(IPLContext handle) override; + void release(IPLContext handle) override; + }; + } +} \ No newline at end of file diff --git a/include/SteamAudio/ObjectPtr.hpp b/include/SteamAudio/ObjectPtr.hpp new file mode 100644 index 0000000..bf79883 --- /dev/null +++ b/include/SteamAudio/ObjectPtr.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include + +#include "SteamAudioLib.h" + +namespace MetaAudio { + namespace SteamAudio { + template + class ObjectPtr { + public: + constexpr ObjectPtr(nullptr_t) noexcept {} + virtual ~ObjectPtr() { + if (m_handle != nullptr) { + release(m_handle); + } + } + + ObjectPtr(const ObjectPtr& other) : ObjectPtr(other.m_handle) { + if (m_handle != nullptr) { + retain(m_handle); + } + } + ObjectPtr& operator=(const ObjectPtr& other) { + if (this == &other) { + return *this; + } + + if (this->m_handle == other.m_handle) { + return *this; + } + retain(other.m_handle); + release(this->m_handle); + this->m_handle = other.m_handle; + return *this; + } + + ObjectPtr(ObjectPtr&& other) : m_handle(std::exchange(other.m_handle, nullptr)) noexcept {} + ObjectPtr& operator=(ObjectPtr&& other) noexcept { + std::swap(m_handle, other.m_handle); + return *this; + } + + bool operator==(ObjectPtr const& other) { + return this->m_handle == other.m_handle; + } + + bool operator!=(ObjectPtr const& other) { + return this->m_handle != other.m_handle; + } + + protected: + virtual void retain(HANDLE handle) { + throw std::logic_error("retain not implemented"); + } + virtual void release(HANDLE handle) { + throw std::logic_error("release not implemented"); + } + HANDLE m_handle = nullptr; + ObjectPtr(HANDLE handle) : m_handle(handle) {} + }; + } +} \ No newline at end of file diff --git a/include/SteamAudio/Scene.hpp b/include/SteamAudio/Scene.hpp new file mode 100644 index 0000000..9589acf --- /dev/null +++ b/include/SteamAudio/Scene.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include "SteamAudioLib.h" +#include "ObjectPtr.hpp" +#include "StaticMesh.hpp" + +namespace MetaAudio { + namespace SteamAudio { + class Simulator; + + class Scene final : public ObjectPtr { + friend class Simulator; + public: + //constexpr Scene() : ObjectPtr() {}; + constexpr Scene(nullptr_t) noexcept : ObjectPtr(nullptr) {} + Scene(IPLScene ptr) : ObjectPtr(ptr) {}; + std::variant StaticMeshCreate(IPLStaticMeshSettings& staticMeshSettings); + void StaticMeshAdd(const StaticMesh& staticMesh); + + void Commit(); + + protected: + // Inherited via ObjectPtr + void retain(IPLScene handle) override; + void release(IPLScene handle) override; + }; + } +} \ No newline at end of file diff --git a/include/SteamAudio/Simulator.hpp b/include/SteamAudio/Simulator.hpp new file mode 100644 index 0000000..9b37259 --- /dev/null +++ b/include/SteamAudio/Simulator.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include "SteamAudioLib.h" +#include "ObjectPtr.hpp" +#include "Scene.hpp" +#include "Source.hpp" + +namespace MetaAudio { + namespace SteamAudio { + class Simulator final : public ObjectPtr { + public: + //constexpr Simulator() : ObjectPtr() {}; + constexpr Simulator(nullptr_t) noexcept : ObjectPtr(nullptr) {} + Simulator(IPLSimulator ptr) : ObjectPtr(ptr) {}; + std::variant SourceCreate(IPLSourceSettings& sourceSettings); + void SourceRemove(const Source& source); + void SetScene(const Scene& scene); + void SourceAdd(const Source& source); + void Commit(); + + void SetSharedInputs(IPLSimulationFlags flags, IPLSimulationSharedInputs& inputs); + void RunDirect(); + + protected: + // Inherited via ObjectPtr + void retain(IPLSimulator handle) override; + void release(IPLSimulator handle) override; + }; + } +} \ No newline at end of file diff --git a/include/SteamAudio/Source.hpp b/include/SteamAudio/Source.hpp new file mode 100644 index 0000000..dd624a7 --- /dev/null +++ b/include/SteamAudio/Source.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include "SteamAudioLib.h" +#include "ObjectPtr.hpp" + +namespace MetaAudio { + namespace SteamAudio { + class Simulator; + + class Source final : public ObjectPtr { + friend class Simulator; + public: + constexpr Source(nullptr_t) noexcept : ObjectPtr(nullptr) {} + Source(IPLSource ptr) : ObjectPtr(ptr) {}; + void SetInputs(IPLSimulationFlags flags, IPLSimulationInputs& inputs); + IPLSimulationOutputs GetOutputs(IPLSimulationFlags flags); + + protected: + // Inherited via ObjectPtr + void retain(IPLSource handle) override; + void release(IPLSource handle) override; + }; + } +} \ No newline at end of file diff --git a/include/SteamAudio/StaticMesh.hpp b/include/SteamAudio/StaticMesh.hpp new file mode 100644 index 0000000..da88898 --- /dev/null +++ b/include/SteamAudio/StaticMesh.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include "SteamAudioLib.h" +#include "ObjectPtr.hpp" + +namespace MetaAudio { + namespace SteamAudio { + class Scene; + + class StaticMesh final : public ObjectPtr { + friend class Scene; + public: + //constexpr StaticMesh() : ObjectPtr() {}; + constexpr StaticMesh(nullptr_t) noexcept : ObjectPtr(nullptr) {} + StaticMesh(IPLStaticMesh ptr) : ObjectPtr(ptr) {}; + + protected: + // Inherited via ObjectPtr + void retain(IPLStaticMesh handle) override; + void release(IPLStaticMesh handle) override; + }; + } +} \ No newline at end of file diff --git a/include/SteamAudioLib.h b/include/SteamAudioLib.h new file mode 100644 index 0000000..79f0e17 --- /dev/null +++ b/include/SteamAudioLib.h @@ -0,0 +1,52 @@ +#pragma once + +extern "C" { +#include "phonon.h" +} + +namespace MetaAudio +{ + class SteamAudioLib + { + public: + decltype(&iplSimulatorCreate) iplSimulatorCreate; + decltype(&iplSimulatorRetain) iplSimulatorRetain; + decltype(&iplSimulatorRelease) iplSimulatorRelease; + decltype(&iplSimulatorSetScene) iplSimulatorSetScene; + decltype(&iplSimulatorCommit) iplSimulatorCommit; + decltype(&iplSimulatorSetSharedInputs) iplSimulatorSetSharedInputs; + decltype(&iplSimulatorRunDirect) iplSimulatorRunDirect; + + decltype(&iplSourceCreate) iplSourceCreate; + decltype(&iplSourceRetain) iplSourceRetain; + decltype(&iplSourceRelease) iplSourceRelease; + decltype(&iplSourceRemove) iplSourceRemove; + decltype(&iplSourceAdd) iplSourceAdd; + decltype(&iplSourceSetInputs) iplSourceSetInputs; + decltype(&iplSourceGetOutputs) iplSourceGetOutputs; + + decltype(&iplSceneCreate) iplSceneCreate; + decltype(&iplSceneRetain) iplSceneRetain; + decltype(&iplSceneRelease) iplSceneRelease; + decltype(&iplSceneCommit) iplSceneCommit; + + decltype(&iplStaticMeshRelease) iplStaticMeshRelease; + decltype(&iplStaticMeshRetain) iplStaticMeshRetain; + decltype(&iplStaticMeshCreate) iplStaticMeshCreate; + decltype(&iplStaticMeshAdd) iplStaticMeshAdd; + decltype(&iplStaticMeshRemove) iplStaticMeshRemove; + + decltype(&iplContextCreate) iplContextCreate; + decltype(&iplContextRelease) iplContextRelease; + decltype(&iplContextRetain) iplContextRetain; + + static SteamAudioLib Load(); + static void Unload(); + + bool IsValid() const; + private: + bool m_valid{ false }; + }; +} + +extern MetaAudio::SteamAudioLib gSteamAudio; \ No newline at end of file diff --git a/include/dynamic_steamaudio.h b/include/dynamic_steamaudio.h deleted file mode 100644 index 02dc051..0000000 --- a/include/dynamic_steamaudio.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include "phonon.h" - -namespace MetaAudio -{ - struct SteamAudio - { - decltype(&iplDestroyEnvironment) iplDestroyEnvironment; - decltype(&iplDestroyScene) iplDestroyScene; - decltype(&iplDestroyStaticMesh) iplDestroyStaticMesh; - decltype(&iplGetDirectSoundPath) iplGetDirectSoundPath; - decltype(&iplSceneCreate) iplSceneCreate; - decltype(&iplStaticMeshCreate) iplStaticMeshCreate; - decltype(&iplEnvironmentCreate) iplEnvironmentCreate; - decltype(&iplContextCreate) iplContextCreate; - decltype(&iplContextRelease) iplContextRelease; - decltype(&iplCleanup) iplCleanup; - }; - -#define SetSteamAudioFunctionPointer(target, library, __function_name__) \ - target.__function_name__ = reinterpret_cast(GetProcAddress(library, #__function_name__)); -} - -extern MetaAudio::SteamAudio gSteamAudio; \ No newline at end of file diff --git a/src/AudioEngine.cpp b/src/AudioEngine.cpp index 1f2ae75..cdea631 100644 --- a/src/AudioEngine.cpp +++ b/src/AudioEngine.cpp @@ -1,3 +1,5 @@ +#include + #include "AudioEngine.hpp" #include "Utilities/VectorUtils.hpp" @@ -11,811 +13,815 @@ namespace MetaAudio { - AudioEngine::AudioEngine(std::shared_ptr cache, std::shared_ptr loader) : m_cache(cache), m_loader(loader) - { - } - - void AudioEngine::S_FreeCache(sfx_t* sfx) - { - aud_sfxcache_t* sc = static_cast(sfx->cache.data); - if (!sc) - return; - - //2015-12-12 fixed a bug that a buffer in use is freed - if (channel_manager->IsPlaying(sfx)) - { - return; - } - - if (sc->buffer) - { - al_context->removeBuffer(sc->buffer); - } - - sfx->cache.data = nullptr; - - m_cache->Cache_Free(sfx->name); - } - - void AudioEngine::S_FlushCaches(void) - { - for (auto& sfx : known_sfx) - { - S_FreeCache(&(sfx.second)); - } - known_sfx.clear(); - } - - sfx_t* AudioEngine::S_FindName(char* name, int* pfInCache) - { - try - { - sfx_t* sfx = nullptr; - - if (!name) - Sys_ErrorEx("S_FindName: NULL\n"); - - if (strlen(name) >= MAX_QPATH) - Sys_ErrorEx("Sound name too long: %s", name); - - auto sfx_iterator = known_sfx.find(name); - if (sfx_iterator != known_sfx.end()) - { - if (pfInCache) - { - *pfInCache = sfx_iterator->second.cache.data != nullptr ? 1 : 0; - } - - if (sfx_iterator->second.servercount > 0) - sfx_iterator->second.servercount = *gAudEngine.cl_servercount; - - return &(sfx_iterator->second); - } - else - { - for (auto& sfxElement : known_sfx) - { - if (sfxElement.second.servercount > 0 && sfxElement.second.servercount != *gAudEngine.cl_servercount) - { - S_FreeCache(&(sfxElement.second)); - known_sfx.erase(sfxElement.first); - break; - } - } - } - - if (!sfx) - { - auto& result = known_sfx.emplace(name, sfx_t()); - sfx = &(result.first->second); - } - else - { - //free OpenAL buffer and cache - S_FreeCache(sfx); - } - - strncpy_s(sfx->name, name, sizeof(sfx->name) - 1); - sfx->name[sizeof(sfx->name) - 1] = 0; - - if (pfInCache) - *pfInCache = 0; - - sfx->servercount = *gAudEngine.cl_servercount; - return sfx; - } - catch (const std::exception& e) - { - MessageBox(NULL, e.what(), "Error on S_FindName", MB_ICONERROR); - exit(0); - } - } - - void AudioEngine::S_CheckWavEnd(aud_channel_t* ch, aud_sfxcache_t* sc) - { - if (ch->voicecache) - { - return; - } - - if (!sc) - return; - - qboolean fWaveEnd = false; - - if (!ch->sound_source->IsPlaying()) - { - fWaveEnd = true; - } - else if (ch->entchannel != CHAN_STREAM) - { - uint64_t iSamplesPlayed = ch->sound_source->GetSampleOffset(); - - if (!sc->looping && iSamplesPlayed >= ch->end) - { - fWaveEnd = true; - ch->sound_source->Stop(); - } - } - - if (!fWaveEnd) - return; - - if (ch->words.size() > 0) - { - sfx_t* sfx = nullptr; - - auto& currentWord = ch->words.front(); - if (currentWord.sfx && !currentWord.fKeepCached) - S_FreeCache(currentWord.sfx); - - ch->words.pop(); - - if (ch->words.size() > 0) - { - ch->sfx = sfx = ch->words.front().sfx; - - if (sfx) - { - sc = m_loader->S_LoadSound(sfx, ch); - - if (sc) - { - ch->start = 0; - ch->end = sc->length; - - vox->TrimStartEndTimes(ch, sc); - if (ch->entchannel == CHAN_STREAM) - { - ch->sound_source = SoundSourceFactory::GetStreamingSource(sc->decoder, al_context->createSource(), 16348, 4); - } - else - { - ch->sound_source = SoundSourceFactory::GetStaticSource(sc->buffer, al_context->createSource()); - } - - ConfigureSource(ch, sc); - ch->sound_source->Play(); - - return; - } - } - } - } - } - - void AudioEngine::SND_Spatialize(aud_channel_t* ch, qboolean init) - { - ch->firstpass = init; - if (!ch->sfx) - return; - - // invalid source - if (!ch->sound_source) - return; - - //apply effect - qboolean underwater = (*gAudEngine.cl_waterlevel > 2) ? true : false; - al_efx->ApplyEffect(ch, underwater); - - //for later usage - aud_sfxcache_t* sc = static_cast(ch->sfx->cache.data); - - //move mouth - if (ch->entnum > 0 && (ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_STREAM)) - { - if (sc && sc->channels == alure::ChannelConfig::Mono && !sc->data.empty()) - { - vox->MoveMouth(ch, sc); - } - } - - //update position - alure::Vector3 alure_position(0, 0, 0); - if (ch->entnum != *gAudEngine.cl_viewentity) - { - ch->sound_source->SetRelative(false); - if (ch->entnum > 0 && ch->entnum < *gAudEngine.cl_num_entities) - { - auto sent = gEngfuncs.GetEntityByIndex(ch->entnum); - - if (sent && sent->model && sent->curstate.messagenum == *gAudEngine.cl_parsecount) - { - VectorCopy(sent->origin, ch->origin); - - if (sent->model->type == mod_brush) - { - // Mobile brushes (such as trains and platforms) have the correct origin set, - // but most other bushes do not. How to correctly detect them? - if (sent->baseline.origin[0] == 0.0f || sent->baseline.origin[1] == 0.0f || sent->baseline.origin[2] == 0.0f) - { - ch->origin[0] = (sent->curstate.mins[0] + sent->curstate.maxs[0]) * 0.5f + sent->curstate.origin[0]; - ch->origin[1] = (sent->curstate.mins[1] + sent->curstate.maxs[1]) * 0.5f + sent->curstate.origin[1]; - ch->origin[2] = (sent->curstate.mins[2] + sent->curstate.maxs[2]) * 0.5f + sent->curstate.origin[2]; - } - } - - float ratio = 0; - if ((*gAudEngine.cl_time) != (*gAudEngine.cl_oldtime)) - { - ratio = static_cast(1 / ((*gAudEngine.cl_time) - (*gAudEngine.cl_oldtime))); - } - - vec3_t sent_velocity = { (sent->curstate.origin[0] - sent->prevstate.origin[0]) * ratio, - (sent->curstate.origin[1] - sent->prevstate.origin[1]) * ratio, - (sent->curstate.origin[2] - sent->prevstate.origin[2]) * ratio }; - - ch->sound_source->SetVelocity(AL_UnpackVector(sent_velocity)); - ch->sound_source->SetRadius(sent->model->radius * AL_UnitToMeters); - } - } - else - { - // It seems that not only sounds from the view entity can be source relative... - if (ch->origin[0] == 0.0f && ch->origin[1] == 0.0f && ch->origin[2] == 0.0f) - { - ch->sound_source->SetRelative(true); - } - } - alure_position = AL_UnpackVector(ch->origin); - } - else - { - ch->sound_source->SetRelative(true); - } - ch->sound_source->SetPosition(alure_position); - - float fvol = 1.0f; - float fpitch = 1.0f; - - vox->SetChanVolPitch(ch, &fvol, &fpitch); - - if (sc && sc->length != 0x40000000) - { - fvol /= (*gAudEngine.g_SND_VoiceOverdrive); - } - - ch->sound_source->SetGain(ch->volume * fvol); - ch->sound_source->SetPitch(ch->pitch * fpitch); - - if (!init) - { - S_CheckWavEnd(ch, sc); - } - } - - void AudioEngine::S_Update(float* origin, float* forward, float* right, float* up) - { - try - { - vec_t orientation[6]; - - // Update Alure's OpenAL context at the start of processing. - al_context->update(); - if (sa_meshloader) - { - sa_meshloader->update(); - } - channel_manager->ClearFinished(); - channel_manager->ClearLoopingRemovedEntities(); - - // Print buffer and clear it. - if (dprint_buffer.length()) - { - gEngfuncs.Con_DPrintf(const_cast((dprint_buffer.c_str()))); - dprint_buffer.clear(); - } - - AL_CopyVector(forward, orientation); - AL_CopyVector(up, orientation + 3); - - alure::Listener al_listener = al_context->getListener(); - if (openal_mute) - { - al_listener.setGain(0.0f); - } - else - { - if (volume) - al_listener.setGain(std::clamp(volume->value, 0.0f, 1.0f)); - else - al_listener.setGain(1.0f); - } - - al_context->setDopplerFactor(std::clamp(al_doppler->value, 0.0f, 10.0f)); - - std::pair alure_orientation( - alure::Vector3(orientation[0], orientation[1], orientation[2]), - alure::Vector3(orientation[3], orientation[4], orientation[5]) - ); - - // Force unit vector if all zeros (Rapture3D workaround). - if (orientation[0] == 0.0f && orientation[1] == 0.0f && orientation[2] == 0.0f) - { - alure_orientation.first[0] = 1; - } - if (orientation[3] == 0.0f && orientation[4] == 0.0f && orientation[5] == 0.0f) - { - alure_orientation.second[0] = 1; - } - - al_efx->SetListenerOrientation(alure_orientation); - - cl_entity_t* pent = gEngfuncs.GetEntityByIndex(*gAudEngine.cl_viewentity); - if (pent != nullptr) - { - float ratio = 0; - if ((*gAudEngine.cl_time) != (*gAudEngine.cl_oldtime)) - { - ratio = static_cast(1 / ((*gAudEngine.cl_time) - (*gAudEngine.cl_oldtime))); - } - - vec3_t view_velocity = { (pent->curstate.origin[0] - pent->prevstate.origin[0]) * ratio, - (pent->curstate.origin[1] - pent->prevstate.origin[1]) * ratio, - (pent->curstate.origin[2] - pent->prevstate.origin[2]) * ratio }; - - al_listener.setVelocity(AL_UnpackVector(view_velocity)); - } - al_listener.setPosition(AL_UnpackVector(origin)); - al_listener.setOrientation(alure_orientation); - - int roomtype = 0; - bool underwater = (*gAudEngine.cl_waterlevel > 2) ? true : false; - if (sxroomwater_type && sxroom_type) - { - roomtype = underwater ? (int)sxroomwater_type->value : (int)sxroom_type->value; - } - al_efx->InterplEffect(roomtype); - - channel_manager->ForEachChannel([&](aud_channel_t& channel) { SND_Spatialize(&channel, false); }); - - if (snd_show && snd_show->value) - { - std::string output; - size_t total = 0; - channel_manager->ForEachChannel([&](aud_channel_t& channel) - { - if (channel.sfx && channel.volume > 0) - { - output.append(std::to_string(static_cast(channel.volume * 255.0f)) + " " + channel.sfx->name + "\n"); - ++total; - } - }); - - if (!output.empty()) - { - output.append("----(" + std::to_string(total) + ")----\n"); - gEngfuncs.Con_Printf(const_cast(output.c_str())); - } - } - } - catch (const std::exception& e) - { - MessageBox(NULL, e.what(), "Error on S_Update", MB_ICONERROR); - exit(0); - } - } - - void AudioEngine::ConfigureSource(aud_channel_t* channel, aud_sfxcache_t* audioData) - { - channel->sound_source->SetOffset(channel->start); - channel->sound_source->SetPitch(channel->pitch); - channel->sound_source->SetRolloffFactors(channel->attenuation, channel->attenuation); - channel->sound_source->SetDistanceRange(0.0f, 1000.0f * AL_UnitToMeters); - channel->sound_source->SetAirAbsorptionFactor(1.0f); - - // Should also set source priority - if (audioData) - { - channel->sound_source->SetLooping(audioData->looping); - } - - SND_Spatialize(channel, true); - } - - void AudioEngine::S_StartSound(int entnum, int entchannel, sfx_t* sfx, float* origin, float fvol, float attenuation, int flags, int pitch, bool is_static) - { - std::string _function_name; - if (is_static) - { - _function_name = "S_StartStaticSound"; - } - else - { - _function_name = "S_StartDynamicSound"; - } - - aud_channel_t* ch{ nullptr }; - aud_sfxcache_t* sc{ nullptr }; - float fpitch; - - if (!sfx) - { - return; - } - - if (nosound && nosound->value) - { - return; - } - - if (sfx->name[0] == '*') - entchannel = CHAN_STREAM; - - if (entchannel == CHAN_STREAM && pitch != 100) - { - gEngfuncs.Con_DPrintf("Warning: pitch shift ignored on stream sound %s\n", sfx->name); - pitch = 100; - } - - if (fvol > 1.0f) - { - gEngfuncs.Con_DPrintf("%s: %s fvolume > 1.0", _function_name, sfx->name); - } - - fpitch = pitch / 100.0f; - - if (flags & (SND_STOP | SND_CHANGE_VOL | SND_CHANGE_PITCH)) - { - if (channel_manager->S_AlterChannel(entnum, entchannel, sfx, fvol, fpitch, flags)) - return; - - if (flags & SND_STOP) - return; - } - - if (pitch == 0) - { - gEngfuncs.Con_DPrintf("Warning: %s Ignored, called with pitch 0", _function_name); - return; - } - - if (is_static) - { - ch = channel_manager->SND_PickStaticChannel(entnum, entchannel, sfx); - } - else - { - ch = channel_manager->SND_PickDynamicChannel(entnum, entchannel, sfx); - } - - if (!ch) - { - return; - } - - VectorCopy(origin, ch->origin); - ch->attenuation = attenuation; - ch->volume = fvol; - ch->entnum = entnum; - ch->entchannel = entchannel; - ch->pitch = fpitch; - - if (sfx->name[0] == '!' || sfx->name[0] == '#') - { - sc = vox->LoadSound(ch, sfx->name + 1); - } - else - { - sc = m_loader->S_LoadSound(sfx, ch); - ch->sfx = sfx; - } - - if (!sc) - { - channel_manager->FreeChannel(ch); - return; - } - - ch->start = 0; - ch->end = sc->length; - - vox->TrimStartEndTimes(ch, sc); - - if (!is_static) - { - vox->InitMouth(entnum, entchannel); - } - - if (ch->entchannel == CHAN_STREAM || (ch->entchannel >= CHAN_NETWORKVOICE_BASE && ch->entchannel <= CHAN_NETWORKVOICE_END)) - { - if (ch->entchannel >= CHAN_NETWORKVOICE_BASE && ch->entchannel <= CHAN_NETWORKVOICE_END) - { - ch->sound_source = SoundSourceFactory::GetStreamingSource(sc->decoder, al_context->createSource(), 1024, 2); - delete sc; // must be deleted here as voice data does not go to the cache to be deleted later - sc = nullptr; - } - else - { - ch->sound_source = SoundSourceFactory::GetStreamingSource(sc->decoder, al_context->createSource(), 4096, 4); - } - } - else - { - if (al_xfi_workaround->value == 2.0f || sc->force_streaming) - { - ch->sound_source = SoundSourceFactory::GetStreamingSource(al_context->createDecoder(sc->buffer.getName()), al_context->createSource(), 16384, 4); - } - else - { - ch->sound_source = SoundSourceFactory::GetStaticSource(sc->buffer, al_context->createSource()); - } - } - - try - { - ConfigureSource(ch, sc); - - ch->sound_source->Play(); - } - catch (const std::runtime_error& error) - { - dprint_buffer.append(_function_name).append(": ").append(error.what()).append("\n"); - } - } - - void AudioEngine::S_StartDynamicSound(int entnum, int entchannel, sfx_t* sfx, float* origin, float fvol, float attenuation, int flags, int pitch) - { - try - { - S_StartSound(entnum, entchannel, sfx, origin, fvol, attenuation, flags, pitch, false); - } - catch (const std::exception& e) - { - MessageBox(NULL, e.what(), "Error on S_StartDynamicSound", MB_ICONERROR); - exit(0); - } - } - - void AudioEngine::S_StartStaticSound(int entnum, int entchannel, sfx_t* sfx, float* origin, float fvol, float attenuation, int flags, int pitch) - { - try - { - S_StartSound(entnum, entchannel, sfx, origin, fvol, attenuation, flags, pitch, true); - } - catch (const std::exception& e) - { - MessageBox(NULL, e.what(), "Error on S_StartStaticSound", MB_ICONERROR); - exit(0); - } - } - - void AudioEngine::S_StopSound(int entnum, int entchannel) - { - try - { - channel_manager->ClearEntityChannels(entnum, entchannel); - } - catch (const std::exception& e) - { - MessageBox(NULL, e.what(), "Error on S_StopSound", MB_ICONERROR); - exit(0); - } - } - - void AudioEngine::S_StopAllSounds(qboolean clear) - { - try - { - if (channel_manager != nullptr) - { - channel_manager->ClearAllChannels(); - } - } - catch (const std::exception& e) - { - MessageBox(NULL, e.what(), "Error on S_StopAllSounds", MB_ICONERROR); - exit(0); - } - } - - bool AudioEngine::OpenAL_Init() - { - try - { - alure::FileIOFactory::set(alure::MakeUnique()); - - al_dev_manager = alure::DeviceManager::getInstance(); - - const char* _al_set_device; - const char* device_set = CommandLine()->CheckParm("-al_device", &_al_set_device); - - if (_al_set_device != nullptr) - al_device = alure::MakeAuto(al_dev_manager.openPlayback(_al_set_device, std::nothrow)); - - if (!al_device) - { - auto default_device = al_dev_manager.defaultDeviceName(alure::DefaultDeviceType::Full); - al_device = alure::MakeAuto(al_dev_manager.openPlayback(default_device)); - } - - strncpy_s(al_device_name, al_device->getName().c_str(), sizeof(al_device_name)); - - al_context = alure::MakeAuto(al_device->createContext()); - - alure::Version ver = al_device->getALCVersion(); - al_device_majorversion = ver.getMajor(); - al_device_minorversion = ver.getMinor(); - - alure::Context::MakeCurrent(al_context->getHandle()); - al_context->setDistanceModel(alure::DistanceModel::Linear); - return true; - } - catch (const std::exception& e) - { - const size_t size = 4096; - - std::stringstream ss; - - ss << "Unable to load. Reason:\n"; - ss << e.what(); - - auto msg = ss.str(); - MessageBox(NULL, msg.c_str(), "OpenAL Error", MB_ICONERROR); - - return false; - } - } - - int AudioEngine::SNDDMA_Init() - { - int r = gAudEngine.SNDDMA_Init(); - - //stop mute me first - openal_mute = false; - - if (!openal_started) - { - if (OpenAL_Init()) - { - openal_started = true; - } - } - - return r; - } - - void AudioEngine::AL_Version() - { - if (openal_started) - gEngfuncs.Con_Printf("%s\n OpenAL Device: %s\n OpenAL Version: %d.%d\n", META_AUDIO_VERSION, al_device_name, al_device_majorversion, al_device_minorversion); - else - gEngfuncs.Con_Printf("%s\n Failed to initalize OpenAL device.\n", META_AUDIO_VERSION, al_device_name, al_device_majorversion, al_device_minorversion); - } - - void AudioEngine::AL_ResetEFX() - { - al_efx.reset(); - al_efx = alure::MakeUnique(*al_context, al_device->getMaxAuxiliarySends(), GetOccluder()); - } - - void AudioEngine::AL_Devices(bool basic) - { - alure::Vector devices; - if (basic) - { - devices = al_dev_manager.enumerate(alure::DeviceEnumeration::Basic); - } - else - { - devices = al_dev_manager.enumerate(alure::DeviceEnumeration::Full); - } - gEngfuncs.Con_Printf("Available OpenAL devices:\n"); - for (const alure::String& device : devices) - { - gEngfuncs.Con_Printf(" %s\n", device.c_str()); - } - } - - static IPLvoid SteamAudioLog(char* message) - { - gEngfuncs.Con_Printf(const_cast(("SteamAudio: " + std::string(message)).c_str())); - } - - void AudioEngine::SteamAudio_Init() - { - if (gSteamAudio.iplCleanup != nullptr) - { - if (sa_context == nullptr) - { - auto error = gSteamAudio.iplCreateContext(SteamAudioLog, nullptr, nullptr, &sa_context); - if (error) - { - throw std::exception("Error creating SA context: " + error); - } - } - sa_simulationSettings.ambisonicsOrder = 1; - sa_simulationSettings.bakingBatchSize = 1; - sa_simulationSettings.irDuration = 1; - sa_simulationSettings.irradianceMinDistance = 0.1f; - sa_simulationSettings.maxConvolutionSources = 1; - sa_simulationSettings.numBounces = 2; - sa_simulationSettings.numDiffuseSamples = 1024; - sa_simulationSettings.maxNumOcclusionSamples = 512; - sa_simulationSettings.numRays = 4096; - sa_simulationSettings.numThreads = std::thread::hardware_concurrency(); - sa_simulationSettings.sceneType = IPLSceneType::IPL_SCENETYPE_PHONON; - sa_meshloader = std::make_shared(sa_context, sa_simulationSettings); - } - } - - void AudioEngine::SteamAudio_Shutdown() - { - if (gSteamAudio.iplCleanup != nullptr) - { - sa_meshloader.reset(); - if (sa_context) - { - gSteamAudio.iplDestroyContext(&sa_context); - sa_context = nullptr; - } - gSteamAudio.iplCleanup(); - gSteamAudio = SteamAudio(); - } - } - - void AudioEngine::S_Init() - { - al_doppler = gEngfuncs.pfnRegisterVariable("al_doppler", "1", FCVAR_EXTDLL); - al_xfi_workaround = gEngfuncs.pfnRegisterVariable("al_xfi_workaround", "0", FCVAR_EXTDLL); - if (gSteamAudio.iplCleanup != nullptr) - { - al_occluder = gEngfuncs.pfnRegisterVariable("al_occluder", "0", FCVAR_EXTDLL); - } - - gAudEngine.S_Init(); - - if (!gEngfuncs.CheckParm("-nosound", NULL)) - { - nosound = gEngfuncs.pfnGetCvarPointer("nosound"); - volume = gEngfuncs.pfnGetCvarPointer("volume"); - sxroomwater_type = gEngfuncs.pfnGetCvarPointer("waterroom_type"); - sxroom_type = gEngfuncs.pfnGetCvarPointer("room_type"); - snd_show = gEngfuncs.pfnGetCvarPointer("snd_show"); - - S_StopAllSounds(true); - } - - SteamAudio_Init(); - AL_ResetEFX(); - - channel_manager = alure::MakeUnique(); - vox = alure::MakeUnique(this, m_loader); - } - - std::shared_ptr AudioEngine::GetOccluder() - { - if (!al_occluder || al_occluder->value == 0.0f) - { - return std::make_shared(*gEngfuncs.pEventAPI); - } - else - { - return std::make_shared(sa_meshloader, *gEngfuncs.pEventAPI); - } - } - - AudioEngine::~AudioEngine() - { - S_StopAllSounds(true); - S_FlushCaches(); - al_efx.reset(); - channel_manager.reset(); - vox.reset(); - - alure::FileIOFactory::set(nullptr); - alure::Context::MakeCurrent(nullptr); - - openal_started = false; - - SteamAudio_Shutdown(); - } - - void AudioEngine::S_Shutdown() - { - //shall we mute OpenAL sound when S_Shutdown? - openal_mute = true; - - gAudEngine.S_Shutdown(); - } + AudioEngine::AudioEngine(std::shared_ptr cache, std::shared_ptr loader) : m_cache(cache), m_loader(loader) + { + } + + void AudioEngine::S_FreeCache(sfx_t* sfx) + { + aud_sfxcache_t* sc = static_cast(sfx->cache.data); + if (!sc) + return; + + //2015-12-12 fixed a bug that a buffer in use is freed + if (channel_manager->IsPlaying(sfx)) + { + return; + } + + if (sc->buffer) + { + al_context->removeBuffer(sc->buffer); + } + + sfx->cache.data = nullptr; + + m_cache->Cache_Free(sfx->name); + } + + void AudioEngine::S_FlushCaches(void) + { + for (auto& sfx : known_sfx) + { + S_FreeCache(&(sfx.second)); + } + known_sfx.clear(); + } + + sfx_t* AudioEngine::S_FindName(char* name, int* pfInCache) + { + try + { + sfx_t* sfx = nullptr; + + if (!name) + Sys_ErrorEx("S_FindName: NULL\n"); + + if (strlen(name) >= MAX_QPATH) + Sys_ErrorEx("Sound name too long: %s", name); + + auto sfx_iterator = known_sfx.find(name); + if (sfx_iterator != known_sfx.end()) + { + if (pfInCache) + { + *pfInCache = sfx_iterator->second.cache.data != nullptr ? 1 : 0; + } + + if (sfx_iterator->second.servercount > 0) + sfx_iterator->second.servercount = *gAudEngine.cl_servercount; + + return &(sfx_iterator->second); + } + else + { + for (auto& sfxElement : known_sfx) + { + if (sfxElement.second.servercount > 0 && sfxElement.second.servercount != *gAudEngine.cl_servercount) + { + S_FreeCache(&(sfxElement.second)); + known_sfx.erase(sfxElement.first); + break; + } + } + } + + if (!sfx) + { + auto& result = known_sfx.emplace(name, sfx_t()); + sfx = &(result.first->second); + } + else + { + //free OpenAL buffer and cache + S_FreeCache(sfx); + } + + strncpy_s(sfx->name, name, sizeof(sfx->name) - 1); + sfx->name[sizeof(sfx->name) - 1] = 0; + + if (pfInCache) + *pfInCache = 0; + + sfx->servercount = *gAudEngine.cl_servercount; + return sfx; + } + catch (const std::exception& e) + { + MessageBox(NULL, e.what(), "Error on S_FindName", MB_ICONERROR); + exit(0); + } + } + + void AudioEngine::S_CheckWavEnd(aud_channel_t* ch, aud_sfxcache_t* sc) + { + if (ch->voicecache) + { + return; + } + + if (!sc) + return; + + qboolean fWaveEnd = false; + + if (!ch->sound_source->IsPlaying()) + { + fWaveEnd = true; + } + else if (ch->entchannel != CHAN_STREAM) + { + uint64_t iSamplesPlayed = ch->sound_source->GetSampleOffset(); + + if (!sc->looping && iSamplesPlayed >= ch->end) + { + fWaveEnd = true; + ch->sound_source->Stop(); + } + } + + if (!fWaveEnd) + return; + + if (ch->words.size() > 0) + { + sfx_t* sfx = nullptr; + + auto& currentWord = ch->words.front(); + if (currentWord.sfx && !currentWord.fKeepCached) + S_FreeCache(currentWord.sfx); + + ch->words.pop(); + + if (ch->words.size() > 0) + { + ch->sfx = sfx = ch->words.front().sfx; + + if (sfx) + { + sc = m_loader->S_LoadSound(sfx, ch); + + if (sc) + { + ch->start = 0; + ch->end = sc->length; + + vox->TrimStartEndTimes(ch, sc); + if (ch->entchannel == CHAN_STREAM) + { + ch->sound_source = SoundSourceFactory::GetStreamingSource(sc->decoder, al_context->createSource(), 16348, 4); + } + else + { + ch->sound_source = SoundSourceFactory::GetStaticSource(sc->buffer, al_context->createSource()); + } + + ConfigureSource(ch, sc); + ch->sound_source->Play(); + + return; + } + } + } + } + } + + void AudioEngine::SND_Spatialize(aud_channel_t* ch, qboolean init) + { + ch->firstpass = init; + if (!ch->sfx) + return; + + // invalid source + if (!ch->sound_source) + return; + + //apply effect + qboolean underwater = (*gAudEngine.cl_waterlevel > 2) ? true : false; + al_efx->ApplyEffect(ch, underwater); + + //for later usage + aud_sfxcache_t* sc = static_cast(ch->sfx->cache.data); + + //move mouth + if (ch->entnum > 0 && (ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_STREAM)) + { + if (sc && sc->channels == alure::ChannelConfig::Mono && !sc->data.empty()) + { + vox->MoveMouth(ch, sc); + } + } + + //update position + alure::Vector3 alure_position(0, 0, 0); + if (ch->entnum != *gAudEngine.cl_viewentity) + { + ch->sound_source->SetRelative(false); + if (ch->entnum > 0 && ch->entnum < *gAudEngine.cl_num_entities) + { + auto sent = gEngfuncs.GetEntityByIndex(ch->entnum); + + if (sent && sent->model && sent->curstate.messagenum == *gAudEngine.cl_parsecount) + { + VectorCopy(sent->origin, ch->origin); + + if (sent->model->type == mod_brush) + { + // Mobile brushes (such as trains and platforms) have the correct origin set, + // but most other bushes do not. How to correctly detect them? + if (sent->baseline.origin[0] == 0.0f || sent->baseline.origin[1] == 0.0f || sent->baseline.origin[2] == 0.0f) + { + ch->origin[0] = (sent->curstate.mins[0] + sent->curstate.maxs[0]) * 0.5f + sent->curstate.origin[0]; + ch->origin[1] = (sent->curstate.mins[1] + sent->curstate.maxs[1]) * 0.5f + sent->curstate.origin[1]; + ch->origin[2] = (sent->curstate.mins[2] + sent->curstate.maxs[2]) * 0.5f + sent->curstate.origin[2]; + } + } + + float ratio = 0; + if ((*gAudEngine.cl_time) != (*gAudEngine.cl_oldtime)) + { + ratio = static_cast(1 / ((*gAudEngine.cl_time) - (*gAudEngine.cl_oldtime))); + } + + vec3_t sent_velocity = { (sent->curstate.origin[0] - sent->prevstate.origin[0]) * ratio, + (sent->curstate.origin[1] - sent->prevstate.origin[1]) * ratio, + (sent->curstate.origin[2] - sent->prevstate.origin[2]) * ratio }; + + ch->sound_source->SetVelocity(AL_UnpackVector(sent_velocity)); + ch->sound_source->SetRadius(sent->model->radius * AL_UnitToMeters); + } + } + else + { + // It seems that not only sounds from the view entity can be source relative... + if (ch->origin[0] == 0.0f && ch->origin[1] == 0.0f && ch->origin[2] == 0.0f) + { + ch->sound_source->SetRelative(true); + } + } + alure_position = AL_UnpackVector(ch->origin); + } + else + { + ch->sound_source->SetRelative(true); + } + ch->sound_source->SetPosition(alure_position); + + float fvol = 1.0f; + float fpitch = 1.0f; + + vox->SetChanVolPitch(ch, &fvol, &fpitch); + + if (sc && sc->length != 0x40000000) + { + fvol /= (*gAudEngine.g_SND_VoiceOverdrive); + } + + ch->sound_source->SetGain(ch->volume * fvol); + ch->sound_source->SetPitch(ch->pitch * fpitch); + + if (!init) + { + S_CheckWavEnd(ch, sc); + } + } + + void AudioEngine::S_Update(float* origin, float* forward, float* right, float* up) + { + try + { + vec_t orientation[6]; + + // Update Alure's OpenAL context at the start of processing. + al_context->update(); + if (sa_meshloader) + { + sa_meshloader->update(); + } + channel_manager->ClearFinished(); + channel_manager->ClearLoopingRemovedEntities(); + + // Print buffer and clear it. + if (dprint_buffer.length()) + { + gEngfuncs.Con_DPrintf(const_cast((dprint_buffer.c_str()))); + dprint_buffer.clear(); + } + + AL_CopyVector(forward, orientation); + AL_CopyVector(up, orientation + 3); + + alure::Listener al_listener = al_context->getListener(); + if (openal_mute) + { + al_listener.setGain(0.0f); + } + else + { + if (volume) + al_listener.setGain(std::clamp(volume->value, 0.0f, 1.0f)); + else + al_listener.setGain(1.0f); + } + + al_context->setDopplerFactor(std::clamp(al_doppler->value, 0.0f, 10.0f)); + + std::pair alure_orientation( + alure::Vector3(orientation[0], orientation[1], orientation[2]), + alure::Vector3(orientation[3], orientation[4], orientation[5]) + ); + + // Force unit vector if all zeros (Rapture3D workaround). + if (orientation[0] == 0.0f && orientation[1] == 0.0f && orientation[2] == 0.0f) + { + alure_orientation.first[0] = 1; + } + if (orientation[3] == 0.0f && orientation[4] == 0.0f && orientation[5] == 0.0f) + { + alure_orientation.second[0] = 1; + } + + al_efx->SetListenerOrientation(alure_orientation); + + cl_entity_t* pent = gEngfuncs.GetEntityByIndex(*gAudEngine.cl_viewentity); + if (pent != nullptr) + { + float ratio = 0; + if ((*gAudEngine.cl_time) != (*gAudEngine.cl_oldtime)) + { + ratio = static_cast(1 / ((*gAudEngine.cl_time) - (*gAudEngine.cl_oldtime))); + } + + vec3_t view_velocity = { (pent->curstate.origin[0] - pent->prevstate.origin[0]) * ratio, + (pent->curstate.origin[1] - pent->prevstate.origin[1]) * ratio, + (pent->curstate.origin[2] - pent->prevstate.origin[2]) * ratio }; + + al_listener.setVelocity(AL_UnpackVector(view_velocity)); + } + al_listener.setPosition(AL_UnpackVector(origin)); + al_listener.setOrientation(alure_orientation); + + int roomtype = 0; + bool underwater = (*gAudEngine.cl_waterlevel > 2) ? true : false; + if (sxroomwater_type && sxroom_type) + { + roomtype = underwater ? (int)sxroomwater_type->value : (int)sxroom_type->value; + } + al_efx->InterplEffect(roomtype); + + channel_manager->ForEachChannel([&](aud_channel_t& channel) { SND_Spatialize(&channel, false); }); + + if (snd_show && snd_show->value) + { + std::string output; + size_t total = 0; + channel_manager->ForEachChannel([&](aud_channel_t& channel) + { + if (channel.sfx && channel.volume > 0) + { + output.append(std::to_string(static_cast(channel.volume * 255.0f)) + " " + channel.sfx->name + "\n"); + ++total; + } + }); + + if (!output.empty()) + { + output.append("----(" + std::to_string(total) + ")----\n"); + gEngfuncs.Con_Printf(const_cast(output.c_str())); + } + } + } + catch (const std::exception& e) + { + MessageBox(NULL, e.what(), "Error on S_Update", MB_ICONERROR); + exit(0); + } + } + + void AudioEngine::ConfigureSource(aud_channel_t* channel, aud_sfxcache_t* audioData) + { + channel->sound_source->SetOffset(channel->start); + channel->sound_source->SetPitch(channel->pitch); + channel->sound_source->SetRolloffFactors(channel->attenuation, channel->attenuation); + channel->sound_source->SetDistanceRange(0.0f, 1000.0f * AL_UnitToMeters); + channel->sound_source->SetAirAbsorptionFactor(1.0f); + + // Should also set source priority + if (audioData) + { + channel->sound_source->SetLooping(audioData->looping); + } + + SND_Spatialize(channel, true); + } + + void AudioEngine::S_StartSound(int entnum, int entchannel, sfx_t* sfx, float* origin, float fvol, float attenuation, int flags, int pitch, bool is_static) + { + std::string _function_name; + if (is_static) + { + _function_name = "S_StartStaticSound"; + } + else + { + _function_name = "S_StartDynamicSound"; + } + + aud_channel_t* ch{ nullptr }; + aud_sfxcache_t* sc{ nullptr }; + float fpitch; + + if (!sfx) + { + return; + } + + if (nosound && nosound->value) + { + return; + } + + if (sfx->name[0] == '*') + entchannel = CHAN_STREAM; + + if (entchannel == CHAN_STREAM && pitch != 100) + { + gEngfuncs.Con_DPrintf("Warning: pitch shift ignored on stream sound %s\n", sfx->name); + pitch = 100; + } + + if (fvol > 1.0f) + { + gEngfuncs.Con_DPrintf("%s: %s fvolume > 1.0", _function_name, sfx->name); + } + + fpitch = pitch / 100.0f; + + if (flags & (SND_STOP | SND_CHANGE_VOL | SND_CHANGE_PITCH)) + { + if (channel_manager->S_AlterChannel(entnum, entchannel, sfx, fvol, fpitch, flags)) + return; + + if (flags & SND_STOP) + return; + } + + if (pitch == 0) + { + gEngfuncs.Con_DPrintf("Warning: %s Ignored, called with pitch 0", _function_name); + return; + } + + if (is_static) + { + ch = channel_manager->SND_PickStaticChannel(entnum, entchannel, sfx); + } + else + { + ch = channel_manager->SND_PickDynamicChannel(entnum, entchannel, sfx); + } + + if (!ch) + { + return; + } + + VectorCopy(origin, ch->origin); + ch->attenuation = attenuation; + ch->volume = fvol; + ch->entnum = entnum; + ch->entchannel = entchannel; + ch->pitch = fpitch; + + if (sfx->name[0] == '!' || sfx->name[0] == '#') + { + sc = vox->LoadSound(ch, sfx->name + 1); + } + else + { + sc = m_loader->S_LoadSound(sfx, ch); + ch->sfx = sfx; + } + + if (!sc) + { + channel_manager->FreeChannel(ch); + return; + } + + ch->start = 0; + ch->end = sc->length; + + vox->TrimStartEndTimes(ch, sc); + + if (!is_static) + { + vox->InitMouth(entnum, entchannel); + } + + if (ch->entchannel == CHAN_STREAM || (ch->entchannel >= CHAN_NETWORKVOICE_BASE && ch->entchannel <= CHAN_NETWORKVOICE_END)) + { + if (ch->entchannel >= CHAN_NETWORKVOICE_BASE && ch->entchannel <= CHAN_NETWORKVOICE_END) + { + ch->sound_source = SoundSourceFactory::GetStreamingSource(sc->decoder, al_context->createSource(), 1024, 2); + delete sc; // must be deleted here as voice data does not go to the cache to be deleted later + sc = nullptr; + } + else + { + ch->sound_source = SoundSourceFactory::GetStreamingSource(sc->decoder, al_context->createSource(), 4096, 4); + } + } + else + { + if (al_xfi_workaround->value == 2.0f || sc->force_streaming) + { + ch->sound_source = SoundSourceFactory::GetStreamingSource(al_context->createDecoder(sc->buffer.getName()), al_context->createSource(), 16384, 4); + } + else + { + ch->sound_source = SoundSourceFactory::GetStaticSource(sc->buffer, al_context->createSource()); + } + } + + try + { + ConfigureSource(ch, sc); + + ch->sound_source->Play(); + } + catch (const std::runtime_error& error) + { + dprint_buffer.append(_function_name).append(": ").append(error.what()).append("\n"); + } + } + + void AudioEngine::S_StartDynamicSound(int entnum, int entchannel, sfx_t* sfx, float* origin, float fvol, float attenuation, int flags, int pitch) + { + try + { + S_StartSound(entnum, entchannel, sfx, origin, fvol, attenuation, flags, pitch, false); + } + catch (const std::exception& e) + { + MessageBox(NULL, e.what(), "Error on S_StartDynamicSound", MB_ICONERROR); + exit(0); + } + } + + void AudioEngine::S_StartStaticSound(int entnum, int entchannel, sfx_t* sfx, float* origin, float fvol, float attenuation, int flags, int pitch) + { + try + { + S_StartSound(entnum, entchannel, sfx, origin, fvol, attenuation, flags, pitch, true); + } + catch (const std::exception& e) + { + MessageBox(NULL, e.what(), "Error on S_StartStaticSound", MB_ICONERROR); + exit(0); + } + } + + void AudioEngine::S_StopSound(int entnum, int entchannel) + { + try + { + channel_manager->ClearEntityChannels(entnum, entchannel); + } + catch (const std::exception& e) + { + MessageBox(NULL, e.what(), "Error on S_StopSound", MB_ICONERROR); + exit(0); + } + } + + void AudioEngine::S_StopAllSounds(qboolean clear) + { + try + { + if (channel_manager != nullptr) + { + channel_manager->ClearAllChannels(); + } + } + catch (const std::exception& e) + { + MessageBox(NULL, e.what(), "Error on S_StopAllSounds", MB_ICONERROR); + exit(0); + } + } + + bool AudioEngine::OpenAL_Init() + { + try + { + alure::FileIOFactory::set(alure::MakeUnique()); + + al_dev_manager = alure::DeviceManager::getInstance(); + + const char* _al_set_device; + const char* device_set = CommandLine()->CheckParm("-al_device", &_al_set_device); + + if (_al_set_device != nullptr) + al_device = alure::MakeAuto(al_dev_manager.openPlayback(_al_set_device, std::nothrow)); + + if (!al_device) + { + auto default_device = al_dev_manager.defaultDeviceName(alure::DefaultDeviceType::Full); + al_device = alure::MakeAuto(al_dev_manager.openPlayback(default_device)); + } + + strncpy_s(al_device_name, al_device->getName().c_str(), sizeof(al_device_name)); + + al_context = alure::MakeAuto(al_device->createContext()); + + alure::Version ver = al_device->getALCVersion(); + al_device_majorversion = ver.getMajor(); + al_device_minorversion = ver.getMinor(); + + alure::Context::MakeCurrent(al_context->getHandle()); + al_context->setDistanceModel(alure::DistanceModel::Linear); + return true; + } + catch (const std::exception& e) + { + const size_t size = 4096; + + std::stringstream ss; + + ss << "Unable to load. Reason:\n"; + ss << e.what(); + + auto msg = ss.str(); + MessageBox(NULL, msg.c_str(), "OpenAL Error", MB_ICONERROR); + + return false; + } + } + + int AudioEngine::SNDDMA_Init() + { + int r = gAudEngine.SNDDMA_Init(); + + //stop mute me first + openal_mute = false; + + if (!openal_started) + { + if (OpenAL_Init()) + { + openal_started = true; + } + } + + return r; + } + + void AudioEngine::AL_Version() + { + if (openal_started) + gEngfuncs.Con_Printf("%s\n OpenAL Device: %s\n OpenAL Version: %d.%d\n", META_AUDIO_VERSION, al_device_name, al_device_majorversion, al_device_minorversion); + else + gEngfuncs.Con_Printf("%s\n Failed to initalize OpenAL device.\n", META_AUDIO_VERSION, al_device_name, al_device_majorversion, al_device_minorversion); + } + + void AudioEngine::AL_ResetEFX() + { + al_efx.reset(); + al_efx = alure::MakeUnique(*al_context, al_device->getMaxAuxiliarySends(), GetOccluder()); + } + + void AudioEngine::AL_Devices(bool basic) + { + alure::Vector devices; + if (basic) + { + devices = al_dev_manager.enumerate(alure::DeviceEnumeration::Basic); + } + else + { + devices = al_dev_manager.enumerate(alure::DeviceEnumeration::Full); + } + gEngfuncs.Con_Printf("Available OpenAL devices:\n"); + for (const alure::String& device : devices) + { + gEngfuncs.Con_Printf(" %s\n", device.c_str()); + } + } + + static void SteamAudioLog(IPLLogLevel level, const char* message) + { + std::stringstream ss; + ss << "SteamAudio - " << level << " - " << message; + gEngfuncs.Con_Printf(const_cast(ss.str().c_str())); + } + + void AudioEngine::SteamAudio_Init() + { + gSteamAudio = SteamAudioLib::Load(); + if (!gSteamAudio.IsValid()) { + SteamAudioLog(IPLLogLevel::IPL_LOGLEVEL_ERROR, "Unable to load SteamAudio library"); + return; + } + + IPLContextSettings settings{}; + settings.version = STEAMAUDIO_VERSION; + settings.logCallback = reinterpret_cast(SteamAudioLog); + auto result = SteamAudio::Context::Create(settings); + if (std::get<1>(result) != IPL_STATUS_SUCCESS) { + SteamAudioLog(IPLLogLevel::IPL_LOGLEVEL_ERROR, "Unable to load SteamAudio"); + return; + } + sa_context = std::get<0>(result); + IPLSimulationSettings simulationSettings{}; + simulationSettings.flags = IPLSimulationFlags::IPL_SIMULATIONFLAGS_DIRECT; + simulationSettings.sceneType = IPLSceneType::IPL_SCENETYPE_DEFAULT; + simulationSettings.reflectionType = IPLReflectionEffectType::IPL_REFLECTIONEFFECTTYPE_PARAMETRIC; // maybe we can use EFX for this one? + simulationSettings.maxNumRays = 4096; + simulationSettings.numDiffuseSamples = 32; + simulationSettings.maxDuration = 2.0f; + simulationSettings.maxOrder = 1; + simulationSettings.maxNumSources = 8; + simulationSettings.maxNumOcclusionSamples = 512; + simulationSettings.numThreads = std::thread::hardware_concurrency(); + simulationSettings.samplingRate = 48000; + simulationSettings.frameSize = 480; + sa_meshloader = std::make_shared(sa_context, simulationSettings); + } + + + void AudioEngine::SteamAudio_Shutdown() + { + if (gSteamAudio.IsValid()) { + sa_meshloader.reset(); + sa_context = nullptr; + gSteamAudio = SteamAudioLib(); + SteamAudioLib::Unload(); + } + } + + void AudioEngine::S_Init() + { + al_doppler = gEngfuncs.pfnRegisterVariable("al_doppler", "1", FCVAR_EXTDLL); + al_xfi_workaround = gEngfuncs.pfnRegisterVariable("al_xfi_workaround", "0", FCVAR_EXTDLL); + if (gSteamAudio.IsValid()) + { + al_occluder = gEngfuncs.pfnRegisterVariable("al_occluder", "0", FCVAR_EXTDLL); + } + + gAudEngine.S_Init(); + + if (!gEngfuncs.CheckParm("-nosound", NULL)) + { + nosound = gEngfuncs.pfnGetCvarPointer("nosound"); + volume = gEngfuncs.pfnGetCvarPointer("volume"); + sxroomwater_type = gEngfuncs.pfnGetCvarPointer("waterroom_type"); + sxroom_type = gEngfuncs.pfnGetCvarPointer("room_type"); + snd_show = gEngfuncs.pfnGetCvarPointer("snd_show"); + + S_StopAllSounds(true); + } + + SteamAudio_Init(); + AL_ResetEFX(); + + channel_manager = alure::MakeUnique(); + vox = alure::MakeUnique(this, m_loader); + } + + std::shared_ptr AudioEngine::GetOccluder() + { + if (!al_occluder || al_occluder->value == 0.0f) + { + return std::make_shared(*gEngfuncs.pEventAPI); + } + else + { + return std::make_shared(sa_meshloader, *gEngfuncs.pEventAPI); + } + } + + AudioEngine::~AudioEngine() + { + S_StopAllSounds(true); + S_FlushCaches(); + al_efx.reset(); + channel_manager.reset(); + vox.reset(); + + alure::FileIOFactory::set(nullptr); + alure::Context::MakeCurrent(nullptr); + + openal_started = false; + + SteamAudio_Shutdown(); + } + + void AudioEngine::S_Shutdown() + { + //shall we mute OpenAL sound when S_Shutdown? + openal_mute = true; + + gAudEngine.S_Shutdown(); + } } \ No newline at end of file diff --git a/src/Effects/SteamAudioOcclusionCalculator.cpp b/src/Effects/SteamAudioOcclusionCalculator.cpp index 0520fac..e99fbec 100644 --- a/src/Effects/SteamAudioOcclusionCalculator.cpp +++ b/src/Effects/SteamAudioOcclusionCalculator.cpp @@ -4,77 +4,104 @@ namespace MetaAudio { - SteamAudioOcclusionCalculator::SteamAudioOcclusionCalculator(std::shared_ptr meshLoader, const event_api_s& event_api) : meshLoader(meshLoader), event_api(event_api) - { - } + SteamAudioOcclusionCalculator::SteamAudioOcclusionCalculator(std::shared_ptr meshLoader, const event_api_s& event_api) : meshLoader(meshLoader), event_api(event_api) + { + } - void SteamAudioOcclusionCalculator::PlayerTrace(Vector3* start, Vector3* end, pmtrace_s& tr) - { - // 0 = regular player hull, 1 = ducked player hull, 2 = point hull - event_api.EV_SetTraceHull(2); - event_api.EV_PlayerTrace(reinterpret_cast(start), reinterpret_cast(end), PM_STUDIO_IGNORE, -1, &tr); - } + void SteamAudioOcclusionCalculator::PlayerTrace(Vector3* start, Vector3* end, pmtrace_s& tr) + { + // 0 = regular player hull, 1 = ducked player hull, 2 = point hull + event_api.EV_SetTraceHull(2); + event_api.EV_PlayerTrace(reinterpret_cast(start), reinterpret_cast(end), PM_STUDIO_IGNORE, -1, &tr); + } - OcclusionFrequencyGain SteamAudioOcclusionCalculator::GetParameters( - Vector3 listenerPosition, - Vector3 listenerAhead, - Vector3 listenerUp, - Vector3 audioSourcePosition, - float sourceRadius, - float attenuationMultiplier - ) - { - if (attenuationMultiplier == ATTN_NONE) - { - return OcclusionFrequencyGain{ 1.0f, 1.0f, 1.0f }; - } + OcclusionFrequencyGain SteamAudioOcclusionCalculator::GetParameters( + Vector3 listenerPosition, + Vector3 listenerAhead, + Vector3 listenerUp, + Vector3 audioSourcePosition, + float sourceRadius, + float attenuationMultiplier + ) + { + if (attenuationMultiplier == ATTN_NONE) + { + return OcclusionFrequencyGain{ 1.0f, 1.0f, 1.0f }; + } - auto toIPLVector3 = [](Vector3* vector) { return *reinterpret_cast(vector); }; + auto toIPLVector3 = [](Vector3* vector) { return *reinterpret_cast(vector); }; - IPLSource source{ toIPLVector3(&audioSourcePosition) }; - auto env = meshLoader->CurrentEnvironment(); - IPLDirectSoundPath result{}; - result = gSteamAudio.iplGetDirectSoundPath( - env, - toIPLVector3(&listenerPosition), - toIPLVector3(&listenerAhead), - toIPLVector3(&listenerUp), - source, - sourceRadius, - SteamAudioOcclusionCalculator::NUMBER_OCCLUSION_RAYS, - IPLDirectOcclusionMode::IPL_DIRECTOCCLUSION_TRANSMISSIONBYFREQUENCY, - IPLDirectOcclusionMethod::IPL_DIRECTOCCLUSION_VOLUMETRIC - ); + auto simulator = meshLoader->CurrentSimulator(); + IPLSourceSettings sourceSettings{}; + sourceSettings.flags = IPLSimulationFlags::IPL_SIMULATIONFLAGS_DIRECT; + auto sourceResult = simulator.SourceCreate(sourceSettings); + if (std::get<1>(sourceResult) != IPLerror::IPL_STATUS_SUCCESS) { + // TODO: LOG + return OcclusionFrequencyGain{ 1.0f, 1.0f, 1.0f }; + } + auto& source = std::get<0>(sourceResult); - if (result.occlusionFactor < 0.5f) //more than half occluded, add transmission component based on obstruction length - { - pmtrace_s tr; + simulator.SourceAdd(source); + simulator.Commit(); - // Convert to GoldSrc coordinates system - auto listener_position = GoldSrc_UnpackVector(listenerPosition); - auto audio_source_position = GoldSrc_UnpackVector(audioSourcePosition); + IPLSimulationInputs sourceInputs{}; + sourceInputs.flags = IPLSimulationFlags::IPL_SIMULATIONFLAGS_DIRECT; + sourceInputs.directFlags = static_cast(IPLDirectSimulationFlags::IPL_DIRECTSIMULATIONFLAGS_OCCLUSION | + IPLDirectSimulationFlags::IPL_DIRECTSIMULATIONFLAGS_TRANSMISSION); + sourceInputs.numOcclusionSamples = SteamAudioOcclusionCalculator::NUMBER_OCCLUSION_RAYS; + sourceInputs.source = { + {1,0,0}, + {0,1,0}, + {0,0,1}, + toIPLVector3(&audioSourcePosition) + }; + sourceInputs.occlusionRadius = sourceRadius; + sourceInputs.occlusionType = IPLOcclusionType::IPL_OCCLUSIONTYPE_VOLUMETRIC; + source.SetInputs(IPLSimulationFlags::IPL_SIMULATIONFLAGS_DIRECT, sourceInputs); - // set up traceline from player eyes to sound emitting entity origin - PlayerTrace(&listener_position, &audio_source_position, tr); + IPLSimulationSharedInputs simulationSharedInputs{}; + simulationSharedInputs.listener = { + {1,0,0}, + toIPLVector3(&listenerUp), + toIPLVector3(&listenerAhead), + toIPLVector3(&listenerPosition) + }; + simulator.SetSharedInputs(IPLSimulationFlags::IPL_SIMULATIONFLAGS_DIRECT, simulationSharedInputs); + simulator.RunDirect(); - // If hit, traceline between ent and player to get solid length. - if ((tr.fraction < 1.0f || tr.allsolid || tr.startsolid) && tr.fraction < 0.99f) - { - alure::Vector3 obstruction_first_point(tr.endpos); - PlayerTrace(&audio_source_position, &listener_position, tr); + auto result = source.GetOutputs(IPLSimulationFlags::IPL_SIMULATIONFLAGS_DIRECT); + simulator.SourceRemove(source); + simulator.Commit(); - if ((tr.fraction < 1.0f || tr.allsolid || tr.startsolid) && tr.fraction < 0.99f && !tr.startsolid) - { - auto distance = obstruction_first_point.getDistance(tr.endpos) * AL_UnitToMeters; - result.transmissionFactor[0] = alure::dBToLinear(-distance * result.transmissionFactor[0]); - result.transmissionFactor[1] = alure::dBToLinear(-distance * result.transmissionFactor[1]); - result.transmissionFactor[2] = alure::dBToLinear(-distance * result.transmissionFactor[2]); - } - } - } + if (result.direct.occlusion < 0.5f) //more than half occluded, add transmission component based on obstruction length + { + pmtrace_s tr; - auto getFactor = [&](size_t index) { return std::clamp((result.occlusionFactor + (1 - result.occlusionFactor) * result.transmissionFactor[index]) / attenuationMultiplier, 0.0f, 1.0f); }; - auto ret = OcclusionFrequencyGain{ getFactor(0), getFactor(1), getFactor(2) }; - return ret; - } + // Convert to GoldSrc coordinates system + auto listener_position = GoldSrc_UnpackVector(listenerPosition); + auto audio_source_position = GoldSrc_UnpackVector(audioSourcePosition); + + // set up traceline from player eyes to sound emitting entity origin + PlayerTrace(&listener_position, &audio_source_position, tr); + + // If hit, traceline between ent and player to get solid length. + if ((tr.fraction < 1.0f || tr.allsolid || tr.startsolid) && tr.fraction < 0.99f) + { + alure::Vector3 obstruction_first_point(tr.endpos); + PlayerTrace(&audio_source_position, &listener_position, tr); + + if ((tr.fraction < 1.0f || tr.allsolid || tr.startsolid) && tr.fraction < 0.99f && !tr.startsolid) + { + auto distance = obstruction_first_point.getDistance(tr.endpos) * AL_UnitToMeters; + result.direct.transmission[0] = alure::dBToLinear(-distance * result.direct.transmission[0]); + result.direct.transmission[1] = alure::dBToLinear(-distance * result.direct.transmission[1]); + result.direct.transmission[2] = alure::dBToLinear(-distance * result.direct.transmission[2]); + } + } + } + + auto getFactor = [&](size_t index) { return std::clamp((result.direct.occlusion + (1 - result.direct.occlusion) * result.direct.transmission[index]) / attenuationMultiplier, 0.0f, 1.0f); }; + auto ret = OcclusionFrequencyGain{ getFactor(0), getFactor(1), getFactor(2) }; + return ret; + } } \ No newline at end of file diff --git a/src/Loaders/GoldSrcFileBuf.cpp b/src/Loaders/GoldSrcFileBuf.cpp index 805ce40..b3ccfb7 100644 --- a/src/Loaders/GoldSrcFileBuf.cpp +++ b/src/Loaders/GoldSrcFileBuf.cpp @@ -67,7 +67,7 @@ namespace MetaAudio } FILESYSTEM_ANY_SEEK(mFile, static_cast(offset), seekType); - auto curPosition = g_pFileSystem->Tell(mFile); + auto curPosition = FILESYSTEM_ANY_TELL(mFile); setg(nullptr, nullptr, nullptr); return curPosition; diff --git a/src/Loaders/SteamAudioMapMeshLoader.cpp b/src/Loaders/SteamAudioMapMeshLoader.cpp index 3523abd..885761f 100644 --- a/src/Loaders/SteamAudioMapMeshLoader.cpp +++ b/src/Loaders/SteamAudioMapMeshLoader.cpp @@ -18,10 +18,9 @@ namespace MetaAudio return (left[0] - right[0]) < EPSILON && (left[1] - right[1]) < EPSILON && (left[2] - right[2]) < EPSILON; } - SteamAudioMapMeshLoader::SteamAudioMapMeshLoader(IPLhandle sa_context, IPLSimulationSettings simulSettings) + SteamAudioMapMeshLoader::SteamAudioMapMeshLoader(SteamAudio::Context sa_context, IPLSimulationSettings simulSettings) : sa_simul_settings(simulSettings), sa_context(sa_context) { - current_map = std::make_unique("", nullptr, nullptr, nullptr); } alure::Vector3 SteamAudioMapMeshLoader::Normalize(const alure::Vector3& vector) @@ -57,13 +56,14 @@ namespace MetaAudio else { auto mapModel = map->model; - if (current_map->Name() == mapModel->name) + if (current_map != nullptr && current_map->Name() == mapModel->name) { return; } std::vector triangles; std::vector vertices; + std::vector materialIndices; for (int i = 0; i < mapModel->nummodelsurfaces; ++i) { @@ -139,6 +139,10 @@ namespace MetaAudio triangle.indices[0] = indexoffset + i + 2; triangle.indices[1] = indexoffset + i + 1; triangle.indices[2] = indexoffset; + + // in the future, each triangle will have its own texture set + // which wil be used to select the proper material + materialIndices.push_back(0); } // Add vertices to final array @@ -146,33 +150,48 @@ namespace MetaAudio } } - IPLhandle scene = nullptr; - IPLerror error = gSteamAudio.iplCreateScene(sa_context, nullptr, IPLSceneType::IPL_SCENETYPE_PHONON, materials.size(), materials.data(), nullptr, nullptr, nullptr, nullptr, nullptr, &scene); - if (error) - { - throw std::runtime_error("Error creating scene: " + std::to_string(error)); + IPLSceneSettings settings{}; + settings.type = IPLSceneType::IPL_SCENETYPE_DEFAULT; + auto sceneResult = this->sa_context.CreateScene(settings); + if (std::get<1>(sceneResult) != IPLerror::IPL_STATUS_SUCCESS) { + // TODO: log error and return instead of throw? + throw std::runtime_error("Error creating scene: " + std::to_string(std::get<1>(sceneResult))); + //return; } - IPLhandle staticmesh = nullptr; - error = gSteamAudio.iplCreateStaticMesh(scene, vertices.size(), triangles.size(), vertices.data(), triangles.data(), std::vector(triangles.size(), 0).data(), &staticmesh); - if (error) - { - throw std::runtime_error("Error creating static mesh: " + std::to_string(error)); + auto& scene = std::get<0>(sceneResult); + IPLStaticMeshSettings staticMeshSettings{}; + staticMeshSettings.numVertices = vertices.size(); + staticMeshSettings.vertices = vertices.data(); + staticMeshSettings.numTriangles = triangles.size(); + staticMeshSettings.triangles = triangles.data(); + staticMeshSettings.numMaterials = materials.size(); + staticMeshSettings.materials = materials.data(); + staticMeshSettings.materialIndices = materialIndices.data(); + + auto staticMeshResult = scene.StaticMeshCreate(staticMeshSettings); + if (std::get<1>(staticMeshResult) != IPLerror::IPL_STATUS_SUCCESS) { + throw std::runtime_error("Error creating static mesh: " + std::to_string(std::get<1>(staticMeshResult))); } + auto& staticMesh = std::get<0>(staticMeshResult); + scene.StaticMeshAdd(staticMesh); + scene.Commit(); - IPLhandle env = nullptr; - error = gSteamAudio.iplCreateEnvironment(sa_context, nullptr, sa_simul_settings, scene, nullptr, &env); - if (error) - { - throw std::runtime_error("Error creating environment: " + std::to_string(error)); + auto simulatorResult = sa_context.CreateSimulator(sa_simul_settings); + if (std::get<1>(simulatorResult) != IPLerror::IPL_STATUS_SUCCESS) { + throw std::runtime_error("Error creating simulator: " + std::to_string(std::get<1>(simulatorResult))); } - current_map = std::make_unique(mapModel->name, env, scene, staticmesh); + auto& simulator = std::get<0>(simulatorResult); + simulator.SetScene(scene); + simulator.Commit(); + + current_map = std::make_unique(mapModel->name, scene, staticMesh, simulator); } } } - IPLhandle SteamAudioMapMeshLoader::CurrentEnvironment() + SteamAudio::Simulator SteamAudioMapMeshLoader::CurrentSimulator() { - return current_map->Env(); + return current_map->Simulator(); } } \ No newline at end of file diff --git a/src/SteamAudio/Context.cpp b/src/SteamAudio/Context.cpp new file mode 100644 index 0000000..0c47e35 --- /dev/null +++ b/src/SteamAudio/Context.cpp @@ -0,0 +1,50 @@ +#include + +#include "SteamAudio/Context.hpp" + +namespace MetaAudio { + namespace SteamAudio { + std::variant Context::Create(IPLContextSettings& contextSettings) { + IPLContext ctx(nullptr); + IPLerror err = gSteamAudio.iplContextCreate(&contextSettings, &ctx); + if (err != IPLerror::IPL_STATUS_SUCCESS) { + return err; + } + + return std::variant(std::in_place_type, ctx); + } + + // try IPL_SCENETYPE_RADEONRAYS, then IPL_SCENETYPE_EMBREE, and then finally IPL_SCENETYPE_DEFAULT + std::variant Context::CreateSimulator(IPLSimulationSettings &simulationSettings) + { + IPLSimulator simulator(nullptr); + IPLerror err = gSteamAudio.iplSimulatorCreate(this->m_handle, &simulationSettings, &simulator); + if (err != IPLerror::IPL_STATUS_SUCCESS) { + return err; + } + + return std::variant(std::in_place_type, simulator); + } + + std::variant Context::CreateScene(IPLSceneSettings& sceneSettings) + { + IPLScene scene(nullptr); + IPLerror err = gSteamAudio.iplSceneCreate(this->m_handle, &sceneSettings, &scene); + if (err != IPLerror::IPL_STATUS_SUCCESS) { + return err; + } + + return std::variant(std::in_place_type, scene); + } + + void Context::retain(IPLContext handle) + { + gSteamAudio.iplContextRetain(handle); + } + + void Context::release(IPLContext handle) + { + gSteamAudio.iplContextRelease(&handle); + } + } +} \ No newline at end of file diff --git a/src/SteamAudio/Scene.cpp b/src/SteamAudio/Scene.cpp new file mode 100644 index 0000000..1b4e570 --- /dev/null +++ b/src/SteamAudio/Scene.cpp @@ -0,0 +1,35 @@ +#include "SteamAudio/Scene.hpp" + +namespace MetaAudio { + namespace SteamAudio { + void Scene::retain(IPLScene handle) + { + gSteamAudio.iplSceneRetain(handle); + } + + void Scene::release(IPLScene handle) + { + gSteamAudio.iplSceneRelease(&handle); + } + + std::variant Scene::StaticMeshCreate(IPLStaticMeshSettings& staticMeshSettings) + { + IPLStaticMesh staticMesh(nullptr); + IPLerror err = gSteamAudio.iplStaticMeshCreate(this->m_handle, &staticMeshSettings, &staticMesh); + if (err != IPLerror::IPL_STATUS_SUCCESS) { + return err; + } + + return std::variant(std::in_place_type, staticMesh); + } + + void Scene::StaticMeshAdd(const StaticMesh& staticMesh) + { + gSteamAudio.iplStaticMeshAdd(staticMesh.m_handle, this->m_handle); + } + + void Scene::Commit() { + gSteamAudio.iplSceneCommit(this->m_handle); + } + } +} diff --git a/src/SteamAudio/Simulator.cpp b/src/SteamAudio/Simulator.cpp new file mode 100644 index 0000000..fb1d999 --- /dev/null +++ b/src/SteamAudio/Simulator.cpp @@ -0,0 +1,52 @@ +#include "SteamAudio/Simulator.hpp" + +namespace MetaAudio { + namespace SteamAudio { + void Simulator::retain(IPLSimulator handle) + { + gSteamAudio.iplSimulatorRetain(handle); + } + + void Simulator::release(IPLSimulator handle) + { + gSteamAudio.iplSimulatorRelease(&handle); + } + + std::variant Simulator::SourceCreate(IPLSourceSettings& sourceSettings) + { + IPLSource source(nullptr); + IPLerror err = gSteamAudio.iplSourceCreate(this->m_handle, &sourceSettings, &source); + if (err != IPLerror::IPL_STATUS_SUCCESS) { + return err; + } + + return std::variant(std::in_place_type, source); + } + + void Simulator::SourceRemove(const Source& source) + { + gSteamAudio.iplSourceRemove(source.m_handle, this->m_handle); + } + + void Simulator::SourceAdd(const Source& source) + { + gSteamAudio.iplSourceAdd(source.m_handle, this->m_handle); + } + + void Simulator::SetScene(const Scene& scene) { + gSteamAudio.iplSimulatorSetScene(this->m_handle, scene.m_handle); + } + + void Simulator::Commit() { + gSteamAudio.iplSimulatorCommit(this->m_handle); + } + + void Simulator::SetSharedInputs(IPLSimulationFlags flags, IPLSimulationSharedInputs& inputs) { + gSteamAudio.iplSimulatorSetSharedInputs(this->m_handle, flags, &inputs); + } + + void Simulator::RunDirect() { + gSteamAudio.iplSimulatorRunDirect(this->m_handle); + } + } +} diff --git a/src/SteamAudio/Source.cpp b/src/SteamAudio/Source.cpp new file mode 100644 index 0000000..21e5631 --- /dev/null +++ b/src/SteamAudio/Source.cpp @@ -0,0 +1,27 @@ +#include "SteamAudio/Source.hpp" + +namespace MetaAudio { + namespace SteamAudio { + void Source::SetInputs(IPLSimulationFlags flags, IPLSimulationInputs& inputs) + { + gSteamAudio.iplSourceSetInputs(this->m_handle, flags, &inputs); + } + + IPLSimulationOutputs Source::GetOutputs(IPLSimulationFlags flags) + { + IPLSimulationOutputs result{}; + gSteamAudio.iplSourceGetOutputs(this->m_handle, flags, &result); + return result; + } + + void Source::retain(IPLSource handle) + { + gSteamAudio.iplSourceRetain(handle); + } + + void Source::release(IPLSource handle) + { + gSteamAudio.iplSourceRelease(&handle); + } + } +} diff --git a/src/SteamAudio/StaticMesh.cpp b/src/SteamAudio/StaticMesh.cpp new file mode 100644 index 0000000..ce1c168 --- /dev/null +++ b/src/SteamAudio/StaticMesh.cpp @@ -0,0 +1,15 @@ +#include "SteamAudio/StaticMesh.hpp" + +namespace MetaAudio { + namespace SteamAudio { + void StaticMesh::retain(IPLStaticMesh handle) + { + gSteamAudio.iplStaticMeshRetain(handle); + } + + void StaticMesh::release(IPLStaticMesh handle) + { + gSteamAudio.iplStaticMeshRelease(&handle); + } + } +} diff --git a/src/SteamAudioLib.cpp b/src/SteamAudioLib.cpp new file mode 100644 index 0000000..badc728 --- /dev/null +++ b/src/SteamAudioLib.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include + +#include "SteamAudioLib.h" + +namespace MetaAudio { + static std::mutex libMutex; + static HMODULE steamAudioLibraryInstance; + +#define SetSteamAudioFunctionPointer(target, library, __function_name__) \ + target.__function_name__ = reinterpret_cast(GetProcAddress(library, #__function_name__)); + + SteamAudioLib SteamAudioLib::Load() { + std::scoped_lock lk(libMutex); + if (steamAudioLibraryInstance == nullptr) { + steamAudioLibraryInstance = LoadLibrary("phonon.dll"); + if (steamAudioLibraryInstance == nullptr) { + return SteamAudioLib(); + } + } + + if (steamAudioLibraryInstance == nullptr) { + return SteamAudioLib(); + } + + SteamAudioLib ret; + // TODO: all pointers + SetSteamAudioFunctionPointer(ret, steamAudioLibraryInstance, iplSimulatorCreate); + SetSteamAudioFunctionPointer(ret, steamAudioLibraryInstance, iplSimulatorRetain); + SetSteamAudioFunctionPointer(ret, steamAudioLibraryInstance, iplSimulatorRelease); + SetSteamAudioFunctionPointer(ret, steamAudioLibraryInstance, iplSimulatorSetScene); + SetSteamAudioFunctionPointer(ret, steamAudioLibraryInstance, iplSimulatorCommit); + + SetSteamAudioFunctionPointer(ret, steamAudioLibraryInstance, iplSourceCreate); + SetSteamAudioFunctionPointer(ret, steamAudioLibraryInstance, iplSourceRetain); + SetSteamAudioFunctionPointer(ret, steamAudioLibraryInstance, iplSourceRelease); + + SetSteamAudioFunctionPointer(ret, steamAudioLibraryInstance, iplSceneCreate); + SetSteamAudioFunctionPointer(ret, steamAudioLibraryInstance, iplSceneRetain); + SetSteamAudioFunctionPointer(ret, steamAudioLibraryInstance, iplSceneRelease); + SetSteamAudioFunctionPointer(ret, steamAudioLibraryInstance, iplSceneCommit); + + SetSteamAudioFunctionPointer(ret, steamAudioLibraryInstance, iplStaticMeshRelease); + SetSteamAudioFunctionPointer(ret, steamAudioLibraryInstance, iplStaticMeshRetain); + SetSteamAudioFunctionPointer(ret, steamAudioLibraryInstance, iplStaticMeshCreate); + SetSteamAudioFunctionPointer(ret, steamAudioLibraryInstance, iplStaticMeshAdd); + SetSteamAudioFunctionPointer(ret, steamAudioLibraryInstance, iplStaticMeshRemove); + + SetSteamAudioFunctionPointer(ret, steamAudioLibraryInstance, iplContextCreate); + SetSteamAudioFunctionPointer(ret, steamAudioLibraryInstance, iplContextRelease); + SetSteamAudioFunctionPointer(ret, steamAudioLibraryInstance, iplContextRetain); + ret.m_valid = true; + + return ret; + } + + void SteamAudioLib::Unload() + { + std::scoped_lock lk(libMutex); + if (steamAudioLibraryInstance != nullptr) { + FreeLibrary(steamAudioLibraryInstance); + steamAudioLibraryInstance = nullptr; + } + } + + bool SteamAudioLib::IsValid() const { + return m_valid; + } +} \ No newline at end of file diff --git a/src/metaaudio.cpp b/src/metaaudio.cpp index f2b3362..f52ff15 100644 --- a/src/metaaudio.cpp +++ b/src/metaaudio.cpp @@ -5,8 +5,9 @@ #include "Loaders/SoundLoader.hpp" #include "Vox/VoxManager.hpp" #include "AudioEngine.hpp" +#include "SteamAudioLib.h" -MetaAudio::SteamAudio gSteamAudio; +MetaAudio::SteamAudioLib gSteamAudio; static std::shared_ptr sound_loader; static std::unique_ptr audio_engine; @@ -22,7 +23,7 @@ mh_enginesave_t *g_pMetaSave = nullptr; IFileSystem *g_pFileSystem = nullptr; IFileSystem_HL25* g_pFileSystem_HL25 = nullptr; -HINSTANCE g_hInstance = nullptr, g_hThisModule = nullptr, g_hEngineModule = nullptr, g_hSteamAudioInstance = nullptr; +HINSTANCE g_hInstance = nullptr, g_hThisModule = nullptr, g_hEngineModule = nullptr; PVOID g_dwEngineBase = 0; DWORD g_dwEngineSize = 0; PVOID g_dwEngineTextBase = 0; @@ -46,21 +47,7 @@ void IPlugins::Init(metahook_api_t *pAPI, mh_interface_t *pInterface, mh_engines g_pMetaSave = pSave; g_hInstance = GetModuleHandle(NULL); - g_hSteamAudioInstance = LoadLibrary("phonon.dll"); - if (g_hSteamAudioInstance) - { - SetSteamAudioFunctionPointer(gSteamAudio, g_hSteamAudioInstance, iplCleanup); - SetSteamAudioFunctionPointer(gSteamAudio, g_hSteamAudioInstance, iplDestroyEnvironment); - SetSteamAudioFunctionPointer(gSteamAudio, g_hSteamAudioInstance, iplDestroyScene); - SetSteamAudioFunctionPointer(gSteamAudio, g_hSteamAudioInstance, iplDestroyStaticMesh); - SetSteamAudioFunctionPointer(gSteamAudio, g_hSteamAudioInstance, iplGetDirectSoundPath); - SetSteamAudioFunctionPointer(gSteamAudio, g_hSteamAudioInstance, iplCreateScene); - SetSteamAudioFunctionPointer(gSteamAudio, g_hSteamAudioInstance, iplCreateStaticMesh); - SetSteamAudioFunctionPointer(gSteamAudio, g_hSteamAudioInstance, iplCreateEnvironment); - SetSteamAudioFunctionPointer(gSteamAudio, g_hSteamAudioInstance, iplCreateContext); - SetSteamAudioFunctionPointer(gSteamAudio, g_hSteamAudioInstance, iplDestroyContext); - } - + gSteamAudio = MetaAudio::SteamAudioLib::Load(); auto audio_cache = std::make_shared(); sound_loader = std::make_shared(audio_cache); audio_engine = std::make_unique(audio_cache, sound_loader); @@ -70,11 +57,8 @@ void IPlugins::Shutdown() { sound_loader.reset(); audio_engine.reset(); - if (g_hSteamAudioInstance) - { - FreeLibrary(g_hSteamAudioInstance); - g_hSteamAudioInstance = nullptr; - } + gSteamAudio = MetaAudio::SteamAudioLib(); + MetaAudio::SteamAudioLib::Unload(); } void IPlugins::LoadEngine()