diff --git a/rootex/core/audio/audio_bus.h b/rootex/core/audio/audio_bus.h new file mode 100644 index 000000000..3f163b216 --- /dev/null +++ b/rootex/core/audio/audio_bus.h @@ -0,0 +1,26 @@ +#pragma once + +#include "common/common.h" + +class AudioBus +{ +private: + String m_BusName; + AudioBus* m_Parent = nullptr; + Vector> m_AudioComponents; + Vector m_Children; + bool m_IsMaster; // really needed? + // TODO: change volume of components when m_Volume changes + float m_Volume; + + AudioBus(); + AudioBus(AudioBus&) = delete; + ~AudioBus() = default; + +public: + void addAudioComponent(Ref cp); + void onVolumeChange(float delta); + void setParent(AudioBus* parent); + String getBusName() { return m_BusName; }; + float& getBusVolume() { return m_Volume; }; +}; diff --git a/rootex/framework/components/audio/audio_component.cpp b/rootex/framework/components/audio/audio_component.cpp index e5457fe26..861d2fcaa 100644 --- a/rootex/framework/components/audio/audio_component.cpp +++ b/rootex/framework/components/audio/audio_component.cpp @@ -28,7 +28,9 @@ AudioComponent::AudioComponent( , m_DependencyOnBoxColliderComponent(this) , m_DependencyOnCapsuleColliderComponent(this) , m_DependencyOnSphereColliderComponent(this) -{ + , m_AudioBus(nullptr) // get the master bus ptr => getMasterBus() +{ + // By default the audio component's bus should be master } RigidBodyComponent* AudioComponent::getCollider() @@ -125,6 +127,23 @@ void AudioComponent::setLooping(bool enabled) m_AudioSource->setLooping(enabled); } +void AudioComponent::setAudioBus(AudioBus* bus) +{ + m_AudioBus = bus; // to be thought upon whether, using BusName here would be better or a pointer + bus->addAudioComponent(Ref(this)); +} + +void AudioComponent::changeVolume(float delta) +{ + if (delta > 1.0f) + { + return; + WARN("Wrong volume change"); + } + + m_Volume = m_Volume * (1.0f - delta); +} + void AudioComponent::draw() { RenderSystem::GetSingleton()->submitSphere(getTransformComponent()->getAbsoluteTransform().Translation(), m_MaxDistance); @@ -176,4 +195,17 @@ void AudioComponent::draw() ImGui::DragFloat("Rolloff Factor", &m_RolloffFactor, 1.0f, 0.0f, 100.0f); ImGui::DragFloat("Max Distance", &m_MaxDistance, 1.0f, 0.0f, 100.0f); ImGui::DragFloat("Volume", &m_Volume, 0.01f, 0.0f, 100.0f); + + // Audio mixer UI + if (ImGui::BeginCombo("Bus", AudioSystem::GetSingleton()->getAudioBuses()[0]->getBusName().c_str())) + { + for (auto& cp : AudioSystem::GetSingleton()->getAudioBuses()) + { + if (ImGui::Selectable(cp->getBusName().c_str())) + { + setAudioBus(cp); + } + } + ImGui::EndCombo(); + } } diff --git a/rootex/framework/components/audio/audio_component.h b/rootex/framework/components/audio/audio_component.h index 9ad57ecc3..a22a2c5e3 100644 --- a/rootex/framework/components/audio/audio_component.h +++ b/rootex/framework/components/audio/audio_component.h @@ -7,6 +7,9 @@ #include "components/physics/box_collider_component.h" #include "components/physics/sphere_collider_component.h" #include "components/physics/capsule_collider_component.h" +#include "systems/audio_system.h" + +class AudioBus; class AudioComponent : public Component { @@ -22,6 +25,7 @@ class AudioComponent : public Component ALfloat m_MaxDistance; ALfloat m_Volume; AudioSource* m_AudioSource; + AudioBus* m_AudioBus; protected: bool m_IsPlayOnStart; @@ -61,5 +65,8 @@ class AudioComponent : public Component bool setupData() override; JSON::json getJSON() const; - void draw() override; + void draw() override; + + void setAudioBus(AudioBus* bus); + void changeVolume(float delta); }; diff --git a/rootex/framework/components/audio/music_component.cpp b/rootex/framework/components/audio/music_component.cpp index 2ce473d7e..71d645c22 100644 --- a/rootex/framework/components/audio/music_component.cpp +++ b/rootex/framework/components/audio/music_component.cpp @@ -1,70 +1,70 @@ -#include "music_component.h" - -DEFINE_COMPONENT(MusicComponent); - -MusicComponent::MusicComponent(Entity& owner, const JSON::json& data) - : AudioComponent( - owner, - data.value("playOnStart", false), - data.value("volume", 1.0f), - data.value("isLooping", false), - data.value("isAttenuated", false), - (AudioSource::AttenuationModel)data.value("attenuationModel", (int)AudioSource::AttenuationModel::Linear), - (ALfloat)data.value("rollOffFactor", 1.0f), - (ALfloat)data.value("referenceDistance", 1.0f), - (ALfloat)data.value("maxDistance", 100.0f)) - , m_AudioFile(ResourceLoader::CreateAudioResourceFile(data.value("audio", "rootex/assets/ball.wav"))) -{ -} - -MusicComponent::~MusicComponent() -{ - m_StreamingAudioSource.reset(); -} - -bool MusicComponent::setupData() -{ - m_StreamingAudioSource.reset(); - m_StreamingAudioBuffer.reset(new StreamingAudioBuffer(m_AudioFile)); - m_StreamingAudioSource.reset(new StreamingAudioSource(m_StreamingAudioBuffer)); - - setAudioSource(m_StreamingAudioSource.get()); - - return AudioComponent::setupData(); -} - -JSON::json MusicComponent::getJSON() const -{ - JSON::json& j = AudioComponent::getJSON(); - - j["audio"] = m_AudioFile->getPath().string(); - j["playOnStart"] = m_IsPlayOnStart; - - return j; -} - -void MusicComponent::setAudioFile(Ref audioFile) -{ - m_AudioFile = audioFile; - setupData(); -} - -void MusicComponent::draw() -{ - ImGui::Text("%s", m_AudioFile->getPath().generic_string().c_str()); - ImGui::SameLine(); - if (ImGui::Button("Audio File")) - { - EventManager::GetSingleton()->call(EditorEvents::EditorOpenFile, VariantVector { m_AudioFile->getPath().generic_string(), (int)m_AudioFile->getType() }); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_ROOTEX_FOLDER_OPEN "##Select Music")) - { - if (Optional result = OS::SelectFile(SupportedFiles.at(ResourceFile::Type::Audio), "game/assets/")) - { - setAudioFile(ResourceLoader::CreateAudioResourceFile(*result)); - } - } - - AudioComponent::draw(); -} +#include "music_component.h" + +DEFINE_COMPONENT(MusicComponent); + +MusicComponent::MusicComponent(Entity& owner, const JSON::json& data) + : AudioComponent( + owner, + data.value("playOnStart", false), + data.value("volume", 1.0f), + data.value("isLooping", false), + data.value("isAttenuated", false), + (AudioSource::AttenuationModel)data.value("attenuationModel", (int)AudioSource::AttenuationModel::Linear), + (ALfloat)data.value("rollOffFactor", 1.0f), + (ALfloat)data.value("referenceDistance", 1.0f), + (ALfloat)data.value("maxDistance", 100.0f)) + , m_AudioFile(ResourceLoader::CreateAudioResourceFile(data.value("audio", "rootex/assets/ball.wav"))) +{ +} + +MusicComponent::~MusicComponent() +{ + m_StreamingAudioSource.reset(); +} + +bool MusicComponent::setupData() +{ + m_StreamingAudioSource.reset(); + m_StreamingAudioBuffer.reset(new StreamingAudioBuffer(m_AudioFile)); + m_StreamingAudioSource.reset(new StreamingAudioSource(m_StreamingAudioBuffer)); + + setAudioSource(m_StreamingAudioSource.get()); + + return AudioComponent::setupData(); +} + +JSON::json MusicComponent::getJSON() const +{ + JSON::json& j = AudioComponent::getJSON(); + + j["audio"] = m_AudioFile->getPath().string(); + j["playOnStart"] = m_IsPlayOnStart; + + return j; +} + +void MusicComponent::setAudioFile(Ref audioFile) +{ + m_AudioFile = audioFile; + setupData(); +} + +void MusicComponent::draw() +{ + ImGui::Text("%s", m_AudioFile->getPath().generic_string().c_str()); + ImGui::SameLine(); + if (ImGui::Button("Audio File")) + { + EventManager::GetSingleton()->call(EditorEvents::EditorOpenFile, VariantVector { m_AudioFile->getPath().generic_string(), (int)m_AudioFile->getType() }); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_ROOTEX_FOLDER_OPEN "##Select Music")) + { + if (Optional result = OS::SelectFile(SupportedFiles.at(ResourceFile::Type::Audio), "game/assets/")) + { + setAudioFile(ResourceLoader::CreateAudioResourceFile(*result)); + } + } + + AudioComponent::draw(); +} diff --git a/rootex/framework/systems/audio_system.cpp b/rootex/framework/systems/audio_system.cpp index f4918abec..34e9eaedb 100644 --- a/rootex/framework/systems/audio_system.cpp +++ b/rootex/framework/systems/audio_system.cpp @@ -144,6 +144,69 @@ void AudioSystem::end() smc.getAudioSource()->stop(); } } +void AudioSystem::addNewBus() +{ + // adds a new bus to the vector and the tree +} + +void AudioSystem::removeBus(AudioBus* bus) +{ + // removes an audio bus from the vector and the tree +} + +AudioBus::AudioBus() + : m_Volume(100.0f) + , m_BusName("Bus " + AudioSystem::GetSingleton()->getAudioBuses().size()) // naming logic to be seen + , m_Parent(nullptr) // if not the first bus, then we need to see how to assign this to the master bus in the starting + , m_IsMaster(false) +{ + m_AudioComponents.clear(); + m_Children.clear(); +} + +void AudioBus::addAudioComponent(Ref cp) +{ + m_AudioComponents.push_back(cp); +} + +void AudioSystem::draw() +{ + System::draw(); + for (int i = 0; i < m_Buses.size(); i++) + { + AudioBus* bus = m_Buses[i]; + ImGui::Text(bus->getBusName().c_str()); + + if (ImGui::Button(ICON_ROOTEX_MINUS "##Remove Bus")) + { + removeBus(bus); + } + + // volume of the bus + ImGui::DragFloat("Volume", &bus->getBusVolume(), 1.0f, 0.0f, 100.0f); + + // selecting the parent + if (ImGui::BeginCombo("Parent", m_Buses[0]->getBusName().c_str())) + { + for (int j = 0; j < i; j++) + { + AudioBus* cp = m_Buses[j]; + if (ImGui::Selectable(cp->getBusName().c_str())) + { + bus->setParent(cp); + } + } + ImGui::EndCombo(); + } + } + + if (ImGui::Button(ICON_ROOTEX_PLUS "##Add Bus")) + { + addNewBus(); + } + + ImGui::Text("AudioMixerSystem"); +} void AudioSystem::setListener(AudioListenerComponent* listenerComponent) { @@ -187,4 +250,25 @@ void AudioSystem::shutDown() AudioSystem::AudioSystem() : System("AudioSystem", UpdateOrder::Async, true) { + m_Buses.clear(); + m_Buses.push_back(nullptr); // add the master audio bus here => new AudioBus(is_Master = true) +} + +void AudioBus::onVolumeChange(float delta) +{ + for (auto& ac : m_AudioComponents) + { + ac->changeVolume(delta); + // the fraction of change is to be decided + } + + for (auto& child : m_Children) + { + child->onVolumeChange(delta); + } +} + +void AudioBus::setParent(AudioBus* parent) +{ + m_Parent = parent; } diff --git a/rootex/framework/systems/audio_system.h b/rootex/framework/systems/audio_system.h index 224ec311e..7f6e0535b 100644 --- a/rootex/framework/systems/audio_system.h +++ b/rootex/framework/systems/audio_system.h @@ -1,87 +1,119 @@ -#pragma once - -#include "common/common.h" - -#include "components/space/transform_component.h" -#include "components/audio/audio_listener_component.h" - -#include "al.h" -#include "alc.h" -#include "alut.h" - -#include "system.h" - -#ifndef ALUT_CHECK -#ifdef _DEBUG -#define ALUT_CHECK(alutFunction) \ - do \ - { \ - alutFunction; \ - AudioSystem::CheckALUTError(#alutFunction, __FILE__, __LINE__); \ - } while (0) -#else -#define ALUT_CHECK(alutFunction) alutFunction -#endif -#endif - -#ifndef AL_CHECK -#ifdef _DEBUG -#define AL_CHECK(alFunction) \ - do \ - { \ - alFunction; \ - AudioSystem::CheckALError(#alFunction, __FILE__, __LINE__); \ - } while (0) -#else -#define AL_CHECK(alFunction) alFunction -#endif -#endif - -class AudioBuffer; -class StaticAudioBuffer; -class StreamingAudioBuffer; - -class AudioSource; -class StreamingAudioSource; - -class ResourceFile; - -/// Audio System responsible for streaming and static audio -class AudioSystem : public System -{ - ALCdevice* m_Device = nullptr; - ALCcontext* m_Context = nullptr; - - AudioListenerComponent* m_Listener = nullptr; - - AudioSystem(); - AudioSystem(AudioSystem&) = delete; - virtual ~AudioSystem() = default; - -public: - static AudioSystem* GetSingleton(); - - /// Returns error string corresponding to AL error codes. - static String GetALErrorString(int errID); - /// Returns error string corresponding to ALC error codes. - static String GetALCErrorString(int errID); - /// Wrapper over alGetError function. - static void CheckALError(const char* msg, const char* fname, int line); - /// Wrapper over alcGetError function. - static void CheckALCError(const char* msg, const char* fname, int line); - /// Wrapper over alutGetError function. - static void CheckALUTError(const char* msg, const char* fname, int line); - - AudioListenerComponent* getListener() const { return m_Listener; } - void setListener(AudioListenerComponent* listenerComponent); - - void restoreListener(); - - bool initialize(const JSON::json& systemData) override; - void setConfig(const SceneSettings& sceneSettings) override; - void shutDown(); - - void update(float deltaMilliseconds) override; - void begin() override; - void end() override; -}; +#pragma once + +#include "common/common.h" + +#include "components/space/transform_component.h" +#include "components/audio/audio_listener_component.h" +#include "components/audio/audio_component.h" + +#include "al.h" +#include "alc.h" +#include "alut.h" + +#include "system.h" + +#ifndef ALUT_CHECK +#ifdef _DEBUG +#define ALUT_CHECK(alutFunction) \ + do \ + { \ + alutFunction; \ + AudioSystem::CheckALUTError(#alutFunction, __FILE__, __LINE__); \ + } while (0) +#else +#define ALUT_CHECK(alutFunction) alutFunction +#endif +#endif + +#ifndef AL_CHECK +#ifdef _DEBUG +#define AL_CHECK(alFunction) \ + do \ + { \ + alFunction; \ + AudioSystem::CheckALError(#alFunction, __FILE__, __LINE__); \ + } while (0) +#else +#define AL_CHECK(alFunction) alFunction +#endif +#endif + +class AudioBuffer; +class StaticAudioBuffer; +class StreamingAudioBuffer; + +class AudioSource; +class StreamingAudioSource; + +class ResourceFile; +class AudioComponent; + +class AudioBus +{ +private: + String m_BusName; + AudioBus* m_Parent = nullptr; + Vector> m_AudioComponents; + Vector m_Children; + bool m_IsMaster; // really needed? + float m_Volume; + + AudioBus(); + AudioBus(AudioBus&) = delete; + ~AudioBus() = default; + +public: + void addAudioComponent(Ref cp); + void onVolumeChange(float delta); + void setParent(AudioBus* parent); + String getBusName() { return m_BusName; }; + float& getBusVolume() { return m_Volume; }; +}; + + +/// Audio System responsible for streaming and static audio +class AudioSystem : public System +{ + ALCdevice* m_Device = nullptr; + ALCcontext* m_Context = nullptr; + + AudioListenerComponent* m_Listener = nullptr; + AudioBus* m_RootAudioBus = nullptr; + Vector m_Buses; + + AudioSystem(); + AudioSystem(AudioSystem&) = delete; + virtual ~AudioSystem() = default; + +public: + static AudioSystem* GetSingleton(); + + /// Returns error string corresponding to AL error codes. + static String GetALErrorString(int errID); + /// Returns error string corresponding to ALC error codes. + static String GetALCErrorString(int errID); + /// Wrapper over alGetError function. + static void CheckALError(const char* msg, const char* fname, int line); + /// Wrapper over alcGetError function. + static void CheckALCError(const char* msg, const char* fname, int line); + /// Wrapper over alutGetError function. + static void CheckALUTError(const char* msg, const char* fname, int line); + + AudioListenerComponent* getListener() const { return m_Listener; } + void setListener(AudioListenerComponent* listenerComponent); + + void restoreListener(); + + bool initialize(const JSON::json& systemData) override; + void setConfig(const SceneSettings& sceneSettings) override; + void shutDown(); + + void update(float deltaMilliseconds) override; + void begin() override; + void end() override; + void draw() override; + + Vector getAudioBuses() const { return m_Buses; }; + void addNewBus(); + void removeBus(AudioBus* bus); +};