diff --git a/README.md b/README.md index 003cb2be..a5681631 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Libraries bundled along with binaries for Windows (MSVC14), available as a submo | **[Xerces-C++](https://xerces.apache.org/xerces-c)** | 3.1.2 | [Apache License Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) | | **[ImGui](https://github.com/ocornut/imgui)** | 2015-12-12 | [MIT License](https://github.com/ocornut/imgui/blob/de3a154f3801de22c8e0bd2aeabf663a70c05972/LICENSE) | | **[nativefiledialog](https://github.com/mlabbe/nativefiledialog) | 2016-01-08 | https://github.com/mlabbe/nativefiledialog/blob/master/LICENSE | +| **[OpenAL](https://www.openal.org/)** | 1.1 | [OpenAL License]() | #### External libraries Libraries that are too big to be bundled with the project. diff --git a/include/Engine/Sound/EContinueSound.h b/include/Engine/Sound/EContinueSound.h new file mode 100644 index 00000000..707d5c6b --- /dev/null +++ b/include/Engine/Sound/EContinueSound.h @@ -0,0 +1,17 @@ +#ifndef Events_ContinueSound_h__ +#define Events_ContinueSound_h__ + +#include "Core/EventBroker.h" +#include "Core/Entity.h" + +namespace Events +{ +// Continues to play a sound from where it was paused. +struct ContinueSound : Event +{ + EntityID EmitterID; +}; + +} + +#endif \ No newline at end of file diff --git a/include/Engine/Sound/EPauseSound.h b/include/Engine/Sound/EPauseSound.h new file mode 100644 index 00000000..33d57428 --- /dev/null +++ b/include/Engine/Sound/EPauseSound.h @@ -0,0 +1,17 @@ +#ifndef Events_PauseSound_h__ +#define Events_PauseSound_h__ + +#include "Core/EventBroker.h" +#include "Core/Entity.h" + +namespace Events +{ +// Pauses a playing sound +struct PauseSound : Event +{ + EntityID EmitterID; +}; + +} + +#endif \ No newline at end of file diff --git a/include/Engine/Sound/EPlayBackgroundMusic.h b/include/Engine/Sound/EPlayBackgroundMusic.h new file mode 100644 index 00000000..711954ee --- /dev/null +++ b/include/Engine/Sound/EPlayBackgroundMusic.h @@ -0,0 +1,18 @@ +#ifndef Events_PlayBackgroundMusic_h__ +#define Events_PlayBackgroundMusic_h__ + +#include +#include "Core/Entity.h" +#include "Core/Event.h" + +namespace Events +{ +// Play a sound that will be heared the same anywhere +struct PlayBackgroundMusic : public Event +{ + std::string FilePath = ""; +}; + +} + +#endif \ No newline at end of file diff --git a/include/Engine/Sound/EPlaySoundOnEntity.h b/include/Engine/Sound/EPlaySoundOnEntity.h new file mode 100644 index 00000000..39dea432 --- /dev/null +++ b/include/Engine/Sound/EPlaySoundOnEntity.h @@ -0,0 +1,21 @@ +#ifndef Events_PlaySoundOnEntity_h__ +#define Events_PlaySoundOnEntity_h__ + +#include +#include "Core/Entity.h" +#include "Core/Event.h" + +namespace Events +{ + +// Plays a sound on an entity with a SoundEmitter component attached. +// Sound behavior is thereby specified in the SoundEmitter component. +struct PlaySoundOnEntity : public Event +{ + EntityID EmitterID = 0; + std::string FilePath = ""; +}; + +} + +#endif \ No newline at end of file diff --git a/include/Engine/Sound/EPlaySoundOnPosition.h b/include/Engine/Sound/EPlaySoundOnPosition.h new file mode 100644 index 00000000..9c6f8f26 --- /dev/null +++ b/include/Engine/Sound/EPlaySoundOnPosition.h @@ -0,0 +1,26 @@ +#ifndef Events_PlaySoundOnPosition_h__ +#define Events_PlaySoundOnPosition_h__ + +#include +#include +#include "Core/Event.h" + +namespace Events +{ + +// Plays a sound on a given position. Idk if this would be useful. +struct PlaySoundOnPosition : public Event +{ + glm::vec3 Position = glm::vec3(0); + std::string FilePath = ""; + float Gain = 1; + float Pitch = 1; + bool Loop = false; + float MaxDistance = 20; + float RollOffFactor = 1; + float ReferenceDistance = 1; +}; + +} + +#endif \ No newline at end of file diff --git a/include/Engine/Sound/ESetBGMGain.h b/include/Engine/Sound/ESetBGMGain.h new file mode 100644 index 00000000..1be6aa24 --- /dev/null +++ b/include/Engine/Sound/ESetBGMGain.h @@ -0,0 +1,16 @@ +#ifndef Events_SetBGMGain_h__ +#define Events_SetBGMGain_h__ + +#include "Core/Event.h" + +namespace Events +{ +// Set the "volume" for all background sounds +struct SetBGMGain : public Event +{ + float Gain; +}; + +} + +#endif \ No newline at end of file diff --git a/include/Engine/Sound/ESetSFXGain.h b/include/Engine/Sound/ESetSFXGain.h new file mode 100644 index 00000000..88465cc1 --- /dev/null +++ b/include/Engine/Sound/ESetSFXGain.h @@ -0,0 +1,16 @@ +#ifndef Events_SetSFXGain_h__ +#define Events_SetSFXGain_h__ + +#include "Core/Event.h" + +namespace Events +{ +// Set the "volume" for all effect sounds +struct SetSFXGain : public Event +{ + float Gain; +}; + +} + +#endif \ No newline at end of file diff --git a/include/Engine/Sound/EStopSound.h b/include/Engine/Sound/EStopSound.h new file mode 100644 index 00000000..62644fbe --- /dev/null +++ b/include/Engine/Sound/EStopSound.h @@ -0,0 +1,17 @@ +#ifndef Events_StopSound_h__ +#define Events_StopSound_h__ + +#include "Core/EventBroker.h" +#include "Core/Entity.h" + +namespace Events +{ +// Stops a sound emitter, and will also delete it. +struct StopSound : Event +{ + EntityID EmitterID; +}; + +} + +#endif \ No newline at end of file diff --git a/include/Engine/Sound/Sound.h b/include/Engine/Sound/Sound.h new file mode 100644 index 00000000..6b2aac04 --- /dev/null +++ b/include/Engine/Sound/Sound.h @@ -0,0 +1,113 @@ +#ifndef Sound_h__ +#define Sound_h__ + +#include "Core/ResourceManager.h" + +class Sound : public Resource +{ + friend class ResourceManager; +public: + Sound(std::string path) { m_Buffer = LoadFile(path); m_Path = path; } + ~Sound() { ClearBuffer(); } + ALuint Buffer() { return m_Buffer; } + std::string Path() { return m_Path; } + float Gain() { return m_Gain; } + void SetGain(float value) { m_Gain = value; } + void ClearBuffer() { alDeleteBuffers(1, &m_Buffer); m_BufferCache.clear(); }; + +private: + float m_Gain = 1; + ALuint m_Buffer; + std::string m_Path; + + // File info + char m_Type[4]; + unsigned long m_Size, m_ChunkSize; + short m_FormatType, m_Channels; + unsigned long m_SampleRate, m_AvgBytesPerSec; + short m_BytesPerSample, m_BitsPerSample; + unsigned int m_DataSize; + std::map m_BufferCache; + + ALuint LoadFile(std::string path) + { + if (m_BufferCache.find(path) != m_BufferCache.end()) { + return m_BufferCache[path]; + } + + // Open file + FILE *fp = fopen(path.c_str(), "rb"); + if (!fp) { + printf("Sound: Failed to open file %s, no such file exists", path.c_str()); + return 0; + } + + //CHECK FOR VALID WAVE-FILE + fread(m_Type, sizeof(char), 4, fp); + if (m_Type[0] != 'R' || m_Type[1] != 'I' || m_Type[2] != 'F' || m_Type[3] != 'F') { + printf("ERROR: No RIFF in WAVE-file"); + return 0; + } + + fread(&m_Size, 4 * sizeof(char), 1, fp); + fread(m_Type, sizeof(char), 4, fp); + if (m_Type[0] != 'W' || m_Type[1] != 'A' || m_Type[2] != 'V' || m_Type[3] != 'E') { + printf("ERROR: Not WAVE-file"); + return 0; + } + + fread(m_Type, sizeof(char), 4, fp); + if (m_Type[0] != 'f' || m_Type[1] != 'm' || m_Type[2] != 't' || m_Type[3] != ' ') { + printf("ERROR: No fmt in WAVE-file"); + return 0; + } + + // READ THE DATA FROM WAVE-FILE + fread(&m_ChunkSize, 4 * sizeof(char), 1, fp); + fread(&m_FormatType, 2 * sizeof(char), 1, fp); + fread(&m_Channels, 2 * sizeof(char), 1, fp); + fread(&m_SampleRate, 4 * sizeof(char), 1, fp); + fread(&m_AvgBytesPerSec, 4 * sizeof(char), 1, fp); + fread(&m_BytesPerSample, 2 * sizeof(char), 1, fp); + fread(&m_BitsPerSample, 2 * sizeof(char), 1, fp); + + fread(m_Type, sizeof(char), 4, fp); + if (m_Type[0] != 'd' || m_Type[1] != 'a' || m_Type[2] != 't' || m_Type[3] != 'a') { + printf("ERROR: WAVE-file Missing data"); + return 0; + } + + fread(&m_DataSize, 4 * sizeof(char), 1, fp); + + unsigned char* buf = new unsigned char[m_DataSize]; + fread(buf, sizeof(char), m_DataSize, fp); + fclose(fp); + + // Create buffer + ALuint format = 0; + if (m_BitsPerSample == 8) { + if (m_Channels == 1) { + format = AL_FORMAT_MONO8; + } else if (m_Channels == 2) { + format = AL_FORMAT_STEREO8; + } + } + if (m_BitsPerSample == 16) { + if (m_Channels == 1) { + format = AL_FORMAT_MONO16; + } else if (m_Channels == 2) { + format = AL_FORMAT_STEREO16; + } + } + + ALuint buffer; + alGenBuffers(1, &buffer); + alBufferData(buffer, format, buf, m_DataSize, m_SampleRate); + delete[] buf; + + m_BufferCache[path] = buffer; + return buffer; + } +}; + +#endif diff --git a/include/Engine/Sound/SoundSystem.h b/include/Engine/Sound/SoundSystem.h new file mode 100644 index 00000000..b9cc2589 --- /dev/null +++ b/include/Engine/Sound/SoundSystem.h @@ -0,0 +1,101 @@ +#ifndef SoundSystem_h__ +#define SoundSystem_h__ + +#include + +#include "glm/common.hpp" +#include "glm/gtx/rotate_vector.hpp" // Calculate Up vector +#include "OpenAL/al.h" +#include "OpenAL/alc.h" + +#include "Core/World.h" +#include "Core/EventBroker.h" +#include "Core/Transform.h" // Absolute transform +#include "Sound/Sound.h" +#include "Sound/EPlaySoundOnEntity.h" +#include "Sound/EPlaySoundOnPosition.h" +#include "Sound/EPlayBackgroundMusic.h" +#include "Sound/EPauseSound.h" +#include "Sound/EContinueSound.h" +#include "Sound/EStopSound.h" +#include "Sound/ESetBGMGain.h" +#include "Sound/ESetSFXGain.h" + +enum class SoundType { + SFX, + BGM +}; + +struct Source +{ + Source() { } + Sound* SoundResource = nullptr; + ALuint ALsource; + SoundType Type; +}; + +class SoundSystem +{ +public: + SoundSystem() { } + SoundSystem(World* world, EventBroker* eventBroker, bool editorMode); + ~SoundSystem(); + // Update emitters / listener + void Update(double dt); +private: + // Help functions for working with OpenaAL + void setListenerPos(glm::vec3 pos) { alListener3f(AL_POSITION, pos.x, pos.y, pos.z); }; + void setListenerVel(glm::vec3 vel) { alListener3f(AL_VELOCITY, vel.x, vel.y, vel.z); }; + void setListenerOri(glm::vec3 ori); + glm::vec3 listnerPos() { glm::vec3 pos; alGetListener3f(AL_POSITION, &pos.x, &pos.y, &pos.z); return pos; }; + glm::vec3 listenerVel() { glm::vec3 vel; alGetListener3f(AL_VELOCITY, &vel.x, &vel.y, &vel.z); return vel; }; + glm::vec3 listenerOri() { glm::vec3 ori; alGetListener3f(AL_ORIENTATION, &ori.x, &ori.y, &ori.z); return ori; }; + void setSourcePos(ALuint source, glm::vec3 pos) { ALfloat spos[3] = { pos.x, pos.y, pos.z }; alSourcefv(source, AL_POSITION, spos); }; + void setSourceVel(ALuint source, glm::vec3 vel) { ALfloat svel[3] = { vel.x, vel.y, vel.z }; alSourcefv(source, AL_VELOCITY, svel); }; + + // Logic + void initOpenAL(); + void updateEmitters(double dt); + void updateListener(double dt); + void deleteInactiveEmitters(); + void addNewEmitters(double dt); + Source* createSource(std::string filePath); + void playSound(Source* source); + void stopSound(Source* source); + void stopEmitters(); + ALenum getSourceState(ALuint source); + void setGain(Source* source, float gain); + void setSoundProperties(ALuint source, ComponentWrapper* soundComponent); + + // OpenAL system variables + ALCdevice* m_ALCdevice = nullptr; + ALCcontext* m_ALCcontext = nullptr; + + // Logic + World* m_World = nullptr; + EventBroker* m_EventBroker = nullptr; + std::unordered_map m_Sources; + float m_BGMVolumeChannel = 1.0f; + float m_SFXVolumeChannel = 1.f; + bool m_EditorEnabled = false; + + // Events + EventRelay m_EPlaySoundOnEntity; + bool OnPlaySoundOnEntity(const Events::PlaySoundOnEntity &e); + EventRelay m_EPlaySoundOnPosition; + bool OnPlaySoundOnPosition(const Events::PlaySoundOnPosition &e); + EventRelay m_EPlayBackgroundMusic; + bool OnPlayBackgroundMusic(const Events::PlayBackgroundMusic &e); + EventRelay m_EPauseSound; + bool OnPauseSound(const Events::PauseSound &e); + EventRelay m_EStopSound; + bool OnStopSound(const Events::StopSound &e); + EventRelay m_EContinueSound; + bool OnContinueSound(const Events::ContinueSound &e); + EventRelay m_ESetBGMGain; + bool OnSetBGMGain(const Events::SetBGMGain &e); // Not tested + EventRelay m_ESetSFXGain; + bool OnSetSFXGain(const Events::SetSFXGain &e); // Not tested +}; + +#endif \ No newline at end of file diff --git a/include/Game/Game.h b/include/Game/Game.h index dfe1cff6..d09d3260 100644 --- a/include/Game/Game.h +++ b/include/Game/Game.h @@ -27,6 +27,8 @@ #include "Network/Server.h" #include "Network/Client.h" +// Sound +#include "Sound/SoundSystem.h" class Game { @@ -56,8 +58,11 @@ private: Network* m_ClientOrServer; bool m_IsClientOrServer = false; - EventRelay m_EInputCommand; - bool debugOnInputCommand(const Events::InputCommand& e); + // Sound + SoundSystem* m_SoundSystem; + + //EventRelay m_EInputCommand; + //bool debugOnInputCommand(const Events::InputCommand& e); void debugInitialize(); void debugTick(double dt); diff --git a/resources/Schema/Components.xsd b/resources/Schema/Components.xsd index 9181daf2..aa62683f 100644 --- a/resources/Schema/Components.xsd +++ b/resources/Schema/Components.xsd @@ -11,4 +11,6 @@ + + \ No newline at end of file diff --git a/resources/Schema/Components/Listener.xml b/resources/Schema/Components/Listener.xml new file mode 100644 index 00000000..7d1fac13 --- /dev/null +++ b/resources/Schema/Components/Listener.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/resources/Schema/Components/Listener.xsd b/resources/Schema/Components/Listener.xsd new file mode 100644 index 00000000..5f71f11a --- /dev/null +++ b/resources/Schema/Components/Listener.xsd @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/resources/Schema/Components/SoundEmitter.xml b/resources/Schema/Components/SoundEmitter.xml new file mode 100644 index 00000000..11093e37 --- /dev/null +++ b/resources/Schema/Components/SoundEmitter.xml @@ -0,0 +1,9 @@ + + + 1.0 + 1.0 + false + 20.0 + 1.0 + 1.0 + \ No newline at end of file diff --git a/resources/Schema/Components/SoundEmitter.xsd b/resources/Schema/Components/SoundEmitter.xsd new file mode 100644 index 00000000..c734e6ba --- /dev/null +++ b/resources/Schema/Components/SoundEmitter.xsd @@ -0,0 +1,25 @@ + + + + + + + + + + + The "volume" of the emitter. A value betweeen 0-1 + + The pitch of the emitter. A value betweeen 0-1 + + If the sound should loop or not. + + The distance where there will no longer be any attenuation. + + The rolloff rate of the source. + + The distance that the source will be the loudest. + + + + \ No newline at end of file diff --git a/resources/Schema/Entities/SoundTestLevel.xml b/resources/Schema/Entities/SoundTestLevel.xml new file mode 100644 index 00000000..366ae81b --- /dev/null +++ b/resources/Schema/Entities/SoundTestLevel.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + Models/Core/UnitCube.obj + + + + + + + + + + + + + + + + + + + + diff --git a/resources/Schema/Types/Entity.xsd b/resources/Schema/Types/Entity.xsd index 138640cc..040a21ea 100644 --- a/resources/Schema/Types/Entity.xsd +++ b/resources/Schema/Types/Entity.xsd @@ -17,6 +17,8 @@ + + diff --git a/src/Engine/CMakeLists.txt b/src/Engine/CMakeLists.txt index 5d74d7d7..277cb617 100644 --- a/src/Engine/CMakeLists.txt +++ b/src/Engine/CMakeLists.txt @@ -9,8 +9,8 @@ find_package(ZLIB REQUIRED) find_package(PNG REQUIRED) find_package(Xerces REQUIRED) # Because FindOpenAL is retarded -#set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} "${CMAKE_SOURCE_DIR}/deps/include/AL") -#find_package(OpenAL REQUIRED) +set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} "${CMAKE_SOURCE_DIR}/deps/include/OpenAL") +find_package(OpenAL REQUIRED) if(UNIX) find_package(X11 REQUIRED) endif() @@ -52,6 +52,12 @@ file(GLOB SOURCE_FILES_Network ) source_group(Network FILES ${SOURCE_FILES_Network}) +file(GLOB SOURCE_FILES_Sound + "${INCLUDE_PATH}/Sound/*.h" + "Sound/*.cpp" +) +source_group(Sound FILES ${SOURCE_FILES_Sound}) + file(GLOB SOURCE_FILES_Rendering "${INCLUDE_PATH}/Rendering/*.h" "Rendering/*.cpp" @@ -86,6 +92,7 @@ set(SOURCE_FILES ${SOURCE_FILES_Core_Util} ${SOURCE_FILES_Input} ${SOURCE_FILES_Network} + ${SOURCE_FILES_Sound} ${SOURCE_FILES_GUI} ${SOURCE_FILES_Rendering} ${SOURCE_FILES_Rendering_Util} diff --git a/src/Engine/Rendering/RenderSystem.cpp b/src/Engine/Rendering/RenderSystem.cpp index 11cbac2c..c8def2b9 100644 --- a/src/Engine/Rendering/RenderSystem.cpp +++ b/src/Engine/Rendering/RenderSystem.cpp @@ -39,11 +39,17 @@ void RenderSystem::switchCamera(EntityID entity) if (m_World->HasComponent(m_CurrentCamera, "Model")) { m_World->GetComponent(m_CurrentCamera, "Model")["Visible"] = true; } + if (m_World->HasComponent(m_CurrentCamera, "Listener")) { + m_World->DeleteComponent(m_CurrentCamera, "Listener"); + } } if (m_World->HasComponent(entity, "Model")) { m_World->GetComponent(entity, "Model")["Visible"] = false; } + if (!m_World->HasComponent(entity, "Listener")) { + m_World->AttachComponent(entity, "Listener"); + } m_CurrentCamera = entity; m_SwitchCamera = false; @@ -162,6 +168,9 @@ void RenderSystem::updateCamera(World* world, double dt) { if (m_SwitchCamera) { auto cameras = world->GetComponents("Camera"); + if (cameras == nullptr) { + return; + } for (auto it = cameras->begin(); it != cameras->end(); it++) { if ((*it).EntityID == m_CurrentCamera) { it++; @@ -173,11 +182,13 @@ void RenderSystem::updateCamera(World* world, double dt) break; } } - ComponentWrapper& cameraComponent = world->GetComponent(m_CurrentCamera, "Camera"); - ComponentWrapper& cameraTransform = world->GetComponent(m_CurrentCamera, "Transform"); + if (m_World->HasComponent(m_CurrentCamera, "Camera")) { + ComponentWrapper& cameraComponent = world->GetComponent(m_CurrentCamera, "Camera"); + ComponentWrapper& cameraTransform = world->GetComponent(m_CurrentCamera, "Transform"); - m_DebugCameraInputController->SetOrientation(glm::quat((glm::vec3)cameraTransform["Orientation"])); - m_DebugCameraInputController->SetPosition(cameraTransform["Position"]); + m_DebugCameraInputController->SetOrientation(glm::quat((glm::vec3)cameraTransform["Orientation"])); + m_DebugCameraInputController->SetPosition(cameraTransform["Position"]); + } } if (m_World->ValidEntity(m_CurrentCamera)) { diff --git a/src/Engine/Sound/SoundSystem.cpp b/src/Engine/Sound/SoundSystem.cpp new file mode 100644 index 00000000..1292e0b1 --- /dev/null +++ b/src/Engine/Sound/SoundSystem.cpp @@ -0,0 +1,304 @@ +#include "Sound/SoundSystem.h" + +SoundSystem::SoundSystem(World* world, EventBroker* eventBroker, bool editorMode) +{ + m_EventBroker = eventBroker; + m_World = world; + m_EditorEnabled = editorMode; + + initOpenAL(); + + alSpeedOfSound(340.29f); + alDistanceModel(AL_LINEAR_DISTANCE); + alDopplerFactor(1); + + EVENT_SUBSCRIBE_MEMBER(m_EPlaySoundOnEntity, &SoundSystem::OnPlaySoundOnEntity); + EVENT_SUBSCRIBE_MEMBER(m_EPlaySoundOnPosition, &SoundSystem::OnPlaySoundOnPosition); + EVENT_SUBSCRIBE_MEMBER(m_EPlayBackgroundMusic, &SoundSystem::OnPlayBackgroundMusic); + EVENT_SUBSCRIBE_MEMBER(m_EStopSound, &SoundSystem::OnStopSound); + EVENT_SUBSCRIBE_MEMBER(m_EPauseSound, &SoundSystem::OnPauseSound); + EVENT_SUBSCRIBE_MEMBER(m_EContinueSound, &SoundSystem::OnContinueSound); + EVENT_SUBSCRIBE_MEMBER(m_ESetBGMGain, &SoundSystem::OnSetBGMGain); + EVENT_SUBSCRIBE_MEMBER(m_ESetSFXGain, &SoundSystem::OnSetSFXGain); +} + +SoundSystem::~SoundSystem() +{ + stopEmitters(); // Stopps emitters + deleteInactiveEmitters(); // Deletes stopped emitters + // Delete entities + std::unordered_map::iterator it; + for (it = m_Sources.begin(); it != m_Sources.end(); it++) { + m_World->DeleteEntity((*it).first); + } + m_Sources.clear(); + + alcDestroyContext(m_ALCcontext); + alcCloseDevice(m_ALCdevice); +} + +void SoundSystem::stopEmitters() +{ + std::unordered_map::iterator it; + for (it = m_Sources.begin(); it != m_Sources.end(); it++) { + if (getSourceState(it->second->ALsource) == AL_PLAYING) { + stopSound(it->second); + } + } +} + +void SoundSystem::Update(double dt) +{ + m_EventBroker->Process(); + addNewEmitters(dt); // can be optimized with "EEntityCreated" + deleteInactiveEmitters(); // can be optimized with "EEntityDeleted" + updateEmitters( dt); + updateListener( dt); +} + +void SoundSystem::deleteInactiveEmitters() +{ + std::unordered_map::iterator it; + for (it = m_Sources.begin(); it != m_Sources.end();) { + if (m_World->ValidEntity(it->first) + && m_World->HasComponent(it->first, "SoundEmitter")) { + if (getSourceState(it->second->ALsource) != AL_STOPPED) { + // Nothing to see here, move along + it++; + continue; + } else { + // Sound has been stopped / finished playing. + alDeleteBuffers(1, &it->second->ALsource); + alDeleteSources(1, &it->second->ALsource); + m_World->DeleteEntity(it->first); + delete it->second; + it = m_Sources.erase(it); + } + } else { + // Entity / Component has been removed + stopSound((*it).second); + alDeleteBuffers(1, &it->second->ALsource); + alDeleteSources(1, &it->second->ALsource); + delete it->second; + it = m_Sources.erase(it); + } + } +} + +void SoundSystem::addNewEmitters(double dt) +{ + auto emitterComponents = m_World->GetComponents("SoundEmitter"); + if (emitterComponents == nullptr) { + return; + } + for (auto it = emitterComponents->begin(); it != emitterComponents->end(); it++) { + EntityID emitter = (*it).EntityID; + std::unordered_map::iterator source; + source = m_Sources.find(emitter); + if (source == m_Sources.end()) { // Did not exist, add it + Source* source = createSource((std::string)(*it)["FilePath"]); + m_Sources[emitter] = source; + } + } +} + +void SoundSystem::updateEmitters(double dt) +{ + std::unordered_map::iterator it; + for (it = m_Sources.begin(); it != m_Sources.end(); it++) { + // Get previous pos + glm::vec3 previousPos; + alGetSource3f(it->second->ALsource, AL_POSITION, &previousPos.x, &previousPos.y, &previousPos.z); + // Get next pos + glm::vec3 nextPos = Transform::AbsolutePosition(m_World, it->first); + // Calculate velocity + glm::vec3 velocity = glm::vec3(nextPos - previousPos) / (float)dt; + setSourcePos(it->second->ALsource, nextPos); + setSourceVel(it->second->ALsource, velocity); + float gain; + (bool)(it->second->Type) ? gain = m_SFXVolumeChannel : gain = m_BGMVolumeChannel; + auto emitter = m_World->GetComponent(it->first, "SoundEmitter"); + setSoundProperties(it->second->ALsource, &emitter); + + // To make an emitter play when spawned in editor mode + if (m_EditorEnabled) { + // Path changed + if (it->second->SoundResource->Path() != (std::string)emitter["FilePath"]) { + it->second->SoundResource = ResourceManager::Load((std::string)emitter["FilePath"]); + if (it->second->SoundResource->Buffer() != 0) { + playSound(it->second); + } + } + } + } +} + +void SoundSystem::updateListener(double dt) +{ + // Should only be one listener. + auto listenerComponents = m_World->GetComponents("Listener"); + if (listenerComponents == nullptr) { + return; + } + for (auto it = listenerComponents->begin(); it != listenerComponents->end(); it++) { + EntityID listener = (*it).EntityID; + glm::vec3 previousPos; + alGetListener3f(AL_POSITION, &previousPos.x, &previousPos.y, &previousPos.z); // Get previous pos + glm::vec3 nextPos = Transform::AbsolutePosition(m_World, listener); // Get next (current) pos + glm::vec3 velocity = glm::vec3(nextPos - previousPos) / (float)dt; // Calculate velocity + setListenerPos(nextPos); + setListenerVel(velocity); + setListenerOri(glm::eulerAngles(Transform::AbsoluteOrientation(m_World, listener))); + } +} + +Source* SoundSystem::createSource(std::string filePath) +{ + ALuint alSource; + alGenSources((ALuint)1, &alSource); + alSourcef(alSource, AL_REFERENCE_DISTANCE, 1.0); + alSourcef(alSource, AL_MAX_DISTANCE, FLT_MAX); + Source* source = new Source(); + source->ALsource = alSource; + source->SoundResource = ResourceManager::Load(filePath); + return source; +} + +void SoundSystem::playSound(Source* source) +{ + alSourcei(source->ALsource, AL_BUFFER, source->SoundResource->Buffer()); + alSourcePlay(source->ALsource); +} + +void SoundSystem::stopSound(Source* source) +{ + alSourceStop(source->ALsource); +} + +bool SoundSystem::OnPlaySoundOnEntity(const Events::PlaySoundOnEntity & e) +{ + Source* source = createSource(e.FilePath); + source->Type = SoundType::SFX; + m_Sources[e.EmitterID] = source; + playSound(source); + return false; +} + +bool SoundSystem::OnPlaySoundOnPosition(const Events::PlaySoundOnPosition & e) +{ + Source* source = createSource(e.FilePath); + auto emitterID = m_World->CreateEntity(); + auto transform = m_World->AttachComponent(emitterID, "Transform"); + (glm::vec3&)transform["Position"] = e.Position; + auto emitter = m_World->AttachComponent(emitterID, "SoundEmitter"); + (float&)(double)emitter["Gain"] = e.Gain; + (float&)(double)emitter["Pitch"] = e.Pitch; + (bool&)emitter["Loop"] = e.Loop; + (float&)(double)emitter["MaxDistance"] = e.MaxDistance; + (float&)(double)emitter["RollOffFactor"] = e.RollOffFactor; + (float&)(double)emitter["ReferenceDistance"] = e.ReferenceDistance; + auto model = m_World->AttachComponent(emitterID, "Model"); + (std::string&)model["Resource"] = "Models/Core/UnitCube.obj"; + source->Type = SoundType::SFX; + m_Sources[emitterID] = source; + playSound(source); + return true; +} + +bool SoundSystem::OnPauseSound(const Events::PauseSound & e) +{ + alSourcePause(m_Sources[e.EmitterID]->ALsource); + return true; +} + +bool SoundSystem::OnStopSound(const Events::StopSound & e) +{ + alSourceStop(m_Sources[e.EmitterID]->ALsource); + return true; +} + +bool SoundSystem::OnContinueSound(const Events::ContinueSound & e) +{ + alSourcePlay(m_Sources[e.EmitterID]->ALsource); + return true; +} + +bool SoundSystem::OnPlayBackgroundMusic(const Events::PlayBackgroundMusic & e) +{ + auto listenerComponents = m_World->GetComponents("Listener"); + for (auto it = listenerComponents->begin(); it != listenerComponents->end(); it++) { + auto emitterChild = m_World->CreateEntity((*it).EntityID); + auto emitter = m_World->AttachComponent(emitterChild, "SoundEmitter"); + (bool&)emitter["Loop"] = true; + (std::string&)emitter["FilePath"] = e.FilePath; + m_World->AttachComponent(emitterChild, "Transform"); + Source* source = createSource(e.FilePath); + source->Type = SoundType::BGM; + m_Sources[emitterChild] = source; + playSound(source); + } + return true; +} + +bool SoundSystem::OnSetBGMGain(const Events::SetBGMGain & e) +{ + m_BGMVolumeChannel = e.Gain; + return true; +} + +bool SoundSystem::OnSetSFXGain(const Events::SetSFXGain & e) +{ + m_SFXVolumeChannel = e.Gain; + return true; +} + +void SoundSystem::setListenerOri(glm::vec3 ori) +{ + // Calculate forward and up vector. + glm::vec3 forward = glm::vec3(0.0, 0.0, -1.0); + forward = glm::rotateX(forward, ori.x); + forward = glm::rotateY(forward, ori.y); + forward = glm::rotateZ(forward, ori.z); + glm::normalize(forward); + glm::vec3 up = glm::vec3(0.0, 1.0, 0.0); + up = glm::rotateX(up, ori.x); + up = glm::rotateY(up, ori.y); + up = glm::rotateZ(up, ori.z); + glm::normalize(up); + ALfloat lOri[6] = { forward.x, forward.y, forward.z, up.x, up.y, up.z }; + alListenerfv(AL_ORIENTATION, lOri); +} + +ALenum SoundSystem::getSourceState(ALuint source) +{ + ALenum state; + alGetSourcei(source, AL_SOURCE_STATE, &state); + return state; +} + +void SoundSystem::setGain(Source * source, float gain) +{ + alSourcef(source->ALsource, AL_GAIN, gain); +} + +void SoundSystem::setSoundProperties(ALuint source, ComponentWrapper* soundComponent) +{ + alSourcef(source, AL_GAIN, (float)(double)(*soundComponent)["Gain"]); + alSourcef(source, AL_PITCH, (float)(double)(*soundComponent)["Pitch"]); + alSourcei(source, AL_LOOPING, (int)(bool)(*soundComponent)["Loop"]); // YOLO + alSourcef(source, AL_MAX_DISTANCE, (float)(double)(*soundComponent)["MaxDistance"]); + alSourcef(source, AL_ROLLOFF_FACTOR, (float)(double)(*soundComponent)["RollOffFactor"]); + alSourcef(source, AL_REFERENCE_DISTANCE, (float)(double)(*soundComponent)["ReferenceDistance"]); +} + +void SoundSystem::initOpenAL() +{ + // Initialize OpenAL + m_ALCdevice = alcOpenDevice(nullptr); + if (m_ALCdevice != nullptr) { + m_ALCcontext = alcCreateContext(m_ALCdevice, nullptr); + alcMakeContextCurrent(m_ALCcontext); + } else { + LOG_ERROR("OpenAL failed to initialize."); + } +} \ No newline at end of file diff --git a/src/Game/Game.cpp b/src/Game/Game.cpp index 96ba3450..c0cad6d3 100644 --- a/src/Game/Game.cpp +++ b/src/Game/Game.cpp @@ -7,6 +7,7 @@ Game::Game(int argc, char* argv[]) { ResourceManager::RegisterType("ConfigFile"); + ResourceManager::RegisterType("Sound"); ResourceManager::RegisterType("Model"); ResourceManager::RegisterType("RawModel"); ResourceManager::RegisterType("Texture"); @@ -83,12 +84,17 @@ Game::Game(int argc, char* argv[]) //boost::thread workerThread(&Game::networkFunction, this); networkFunction(); } + + // Invoke sound system + m_SoundSystem = new SoundSystem(m_World, m_EventBroker, m_Config->Get("Debug.EditorEnabled", false)); + m_LastTime = glfwGetTime(); } Game::~Game() { delete m_SystemPipeline; + delete m_SoundSystem; delete m_World; delete m_FrameStack; delete m_InputProxy; @@ -122,9 +128,10 @@ void Game::Tick() } // Iterate through systems and update world! m_SystemPipeline->Update(m_World, dt); + debugTick(dt); m_Renderer->Update(dt); m_EventBroker->Process(); - + m_SoundSystem->Update(dt); GLERROR("Game::Tick m_RenderQueueFactory->Update"); m_Renderer->Draw(*m_RenderFrame); GLERROR("Game::Tick m_Renderer->Draw");