diff --git a/TSE_Core/src/BehaviourScripts/AudioListener.cpp b/TSE_Core/src/BehaviourScripts/AudioListener.cpp new file mode 100644 index 0000000..d4b1bd2 --- /dev/null +++ b/TSE_Core/src/BehaviourScripts/AudioListener.cpp @@ -0,0 +1,10 @@ +#include "AudioListener.hpp" +#include "Vector3.hpp" +#include "elements/Transformable.hpp" +#include "elements/AudioEngine.hpp" + +void TSE::AudioListener::OnUpdate() +{ + Vector3 pos = baseObject->GetGlobalPosition(); + ma_engine_listener_set_position(AudioEngine::engine, 0, pos.x, pos.y, -pos.z); +} \ No newline at end of file diff --git a/TSE_Core/src/BehaviourScripts/AudioListener.hpp b/TSE_Core/src/BehaviourScripts/AudioListener.hpp new file mode 100644 index 0000000..2fa5fa9 --- /dev/null +++ b/TSE_Core/src/BehaviourScripts/AudioListener.hpp @@ -0,0 +1,17 @@ +#pragma once + +#define AUDIOLISTENER typeid(AudioListener).name() + +#include "elements/BehaviourScript.hpp" + +namespace TSE +{ + class AudioListener : public BehaviourScript + { + void OnUpdate() override; + inline const char* GetName() override + { + return "Audio Listener"; + } + }; +} // namespace TSE diff --git a/TSE_Core/src/BehaviourScripts/AudioSource.cpp b/TSE_Core/src/BehaviourScripts/AudioSource.cpp new file mode 100644 index 0000000..15d295a --- /dev/null +++ b/TSE_Core/src/BehaviourScripts/AudioSource.cpp @@ -0,0 +1,135 @@ +#include "AudioSource.hpp" +#include "Vector3.hpp" +#include "elements/Transformable.hpp" + +float TSE::AudioSource::GetMinDistance() +{ + return minDistance; +} + +float TSE::AudioSource::GetMaxDistance() +{ + return maxDistance; +} + +bool TSE::AudioSource::GetGlobal() +{ + return global; +} + +void TSE::AudioSource::SetGlobal(bool v) +{ + if(global != v) + { + auto it = sounds.begin(); + for (int j = 0; j < sounds.size(); j++) + { + it->second; + ma_sound_set_spatialization_enabled(it->second, !v); + it++; + } + } + global = v; +} + +void TSE::AudioSource::SetMinDistance(float v) +{ + minDistance = v; + auto it = sounds.begin(); + for (int j = 0; j < sounds.size(); j++) + { + it->second; + ma_sound_set_min_distance(it->second, minDistance); + it++; + } +} + +void TSE::AudioSource::SetMaxDistance(float v) +{ + maxDistance = v; + auto it = sounds.begin(); + for (int j = 0; j < sounds.size(); j++) + { + it->second; + ma_sound_set_max_distance(it->second, maxDistance); + it++; + } +} + +void TSE::AudioSource::AddClip(AudioClip *clip) +{ + clips[clip->name] = clip; + ma_sound* s = clip->GetAudioSound(); + ma_sound_set_spatialization_enabled(s, !global); + ma_sound_set_attenuation_model(s, ma_attenuation_model_linear); + ma_sound_set_rolloff(s, 1.0f); + ma_sound_set_min_distance(s, minDistance); + ma_sound_set_max_distance(s, maxDistance); + ma_sound_set_min_gain(s, 0.0f); + ma_sound_set_max_gain(s, 1.0f); + sounds[clip->name] = s; +} + +void TSE::AudioSource::RemoveClip(std::string name) +{ + if(currentlyPlaying == name) StopPlaying(); + clips[name]->DestroyAudioSound(sounds[name]); + clips.erase(name); + sounds.erase(name); +} + +void TSE::AudioSource::StartClip(std::string name, bool forceRestart) +{ + if(currentlyPlaying != name){ + StopPlaying(); + } + else if(forceRestart) + { + ma_sound_seek_to_pcm_frame(sounds[name], 0); + } + ma_sound_start(sounds[name]); + currentlyPlaying = name; +} + +void TSE::AudioSource::StopPlaying() +{ + if(currentlyPlaying == "") return; + ma_sound_stop(sounds[currentlyPlaying]); + ma_sound_seek_to_pcm_frame(sounds[currentlyPlaying], 0); + currentlyPlaying = ""; +} + +void TSE::AudioSource::PausePlaying() +{ + if(currentlyPlaying == "") return; + ma_sound_stop(sounds[currentlyPlaying]); +} + +TSE::AudioClip *TSE::AudioSource::GetClipAt(int i) +{ + auto it = clips.begin(); + for (int j = 0; j < i; j++) + { + it++; + } + return it->second; +} + +TSE::AudioSource::~AudioSource() +{ + int count = clips.size(); + for (int i = 0; i < count; i++) + { + std::string name = GetClipAt(0)->name; + RemoveClip(name); + } +} + +void TSE::AudioSource::OnUpdate() +{ + if(!global) + { + Vector3 pos = baseObject->GetGlobalPosition(); + ma_sound_set_position(sounds[currentlyPlaying], pos.x, pos.y, -pos.z); + } +} diff --git a/TSE_Core/src/BehaviourScripts/AudioSource.hpp b/TSE_Core/src/BehaviourScripts/AudioSource.hpp new file mode 100644 index 0000000..c7b241b --- /dev/null +++ b/TSE_Core/src/BehaviourScripts/AudioSource.hpp @@ -0,0 +1,50 @@ +#pragma once + +#define AUDIOSOURCE typeid(AudioSource).name() + +#include "Types.hpp" +#include "elements/AudioClip.hpp" +#include "elements/BehaviourScript.hpp" +#include "miniaudio.h" +#include + +namespace TSE +{ + class AudioSource : public BehaviourScript + { + public: + std::unordered_map clips; + string currentlyPlaying = ""; + + private: + bool global = false; + float minDistance = 5; + float maxDistance = 8; + std::unordered_map sounds; + + public: + float GetMinDistance(); + float GetMaxDistance(); + bool GetGlobal(); + void SetGlobal(bool v); + void SetMinDistance(float v); + void SetMaxDistance(float v); + + void AddClip(AudioClip* clip); + void RemoveClip(std::string name); + + void StartClip(std::string name, bool forceRestart = true); + void StopPlaying(); + void PausePlaying(); + + AudioClip* GetClipAt(int i); + + ~AudioSource(); + + void OnUpdate() override; + inline const char* GetName() override + { + return "Audio Source"; + } + }; +} // namespace TSE diff --git a/TSE_Core/src/elements/AudioClip.cpp b/TSE_Core/src/elements/AudioClip.cpp new file mode 100644 index 0000000..a7f659a --- /dev/null +++ b/TSE_Core/src/elements/AudioClip.cpp @@ -0,0 +1,70 @@ +#include "AudioClip.hpp" +#include "Debug.hpp" +#include +#include "AudioEngine.hpp" + +TSE::AudioClip::AudioClip(string path) +{ + description.path = path; +} + +TSE::AudioClip::AudioClip(byte *stream, size_t size) +{ + description.audiostream = stream; + description.dataSize = size; +} + +ma_sound *TSE::AudioClip::GetAudioSound() +{ + if(description.path != ""){ + if(std::filesystem::exists(description.path)) + { + ma_result res; + ma_sound* sound = (ma_sound*)malloc(sizeof(*sound)); + + res = ma_sound_init_from_file(AudioEngine::engine, description.path.c_str(), 0, nullptr, nullptr, sound); + if (res != MA_SUCCESS) { + TSE_WARNING("ma_sound_init_from_file failed: " + std::to_string(res)); + delete(sound); + return nullptr; + } + return sound; + } + else + { + TSE_WARNING("Can't create ma_sound because the given path dose not exist: \n" + description.path); + } + } + else if(description.audiostream != nullptr && description.dataSize > 0) + { + ma_result res; + ma_decoder m_decoder; + res = ma_decoder_init_memory(description.audiostream, description.dataSize, nullptr, &m_decoder); + if (res != MA_SUCCESS) { + TSE_WARNING("ma_decoder_init_memory failed: " + std::to_string(res)); + return nullptr; + } + ma_sound* sound = (ma_sound*)malloc(sizeof(*sound)); + res = ma_sound_init_from_data_source(AudioEngine::engine, &m_decoder, MA_SOUND_FLAG_STREAM, nullptr, sound); + ma_decoder_uninit(&m_decoder); + if (res != MA_SUCCESS) { + TSE_WARNING("ma_sound_init_from_data_source failed: " + std::to_string(res)); + delete(sound); + return nullptr; + } + return sound; + } + else + { + TSE_WARNING("Can't create ma_sound because description is not set, or unconfigured."); + } + return nullptr; +} + +void TSE::AudioClip::DestroyAudioSound(ma_sound *sound) +{ + if (!sound) return; + + ma_sound_uninit(sound); + delete sound; +} diff --git a/TSE_Core/src/elements/AudioClip.hpp b/TSE_Core/src/elements/AudioClip.hpp new file mode 100644 index 0000000..0cac3d4 --- /dev/null +++ b/TSE_Core/src/elements/AudioClip.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "Types.hpp" +#include "miniaudio.h" + +namespace TSE +{ + struct AudioClipDescription + { + public: + string path = ""; + byte* audiostream = nullptr; + size_t dataSize = 0; + }; + + class AudioClip + { + public: + string name = ""; + AudioClipDescription description; + + AudioClip(string path); + AudioClip(byte* stream, size_t size); + + ma_sound* GetAudioSound(); + void DestroyAudioSound(ma_sound* sound); + }; +} // namespace TSE diff --git a/TSE_Core/src/elements/AudioEngine.cpp b/TSE_Core/src/elements/AudioEngine.cpp new file mode 100644 index 0000000..c566b96 --- /dev/null +++ b/TSE_Core/src/elements/AudioEngine.cpp @@ -0,0 +1,22 @@ +#include "AudioEngine.hpp" +#include "Debug.hpp" + +ma_engine* TSE::AudioEngine::engine = nullptr; + +void TSE::AudioEngine::Init() +{ + ma_result res; + engine = (ma_engine*)malloc(sizeof(*engine)); + res = ma_engine_init(nullptr, engine); + if(res != MA_SUCCESS) + { + TSE_WARNING("Couldn't init audio engine. error code: " + std::to_string(res)); + } +} + +void TSE::AudioEngine::Destroy() +{ + ma_engine_uninit(engine); + delete(engine); + engine = nullptr; +} diff --git a/TSE_Core/src/elements/AudioEngine.hpp b/TSE_Core/src/elements/AudioEngine.hpp new file mode 100644 index 0000000..351b61f --- /dev/null +++ b/TSE_Core/src/elements/AudioEngine.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "miniaudio.h" + +namespace TSE +{ + class AudioEngine + { + public: + static ma_engine* engine; + + static void Init(); + static void Destroy(); + }; +} // namespace TSE diff --git a/TSE_Core/src/extern/miniaudio.c b/TSE_Core/src/extern/miniaudio.c new file mode 100644 index 0000000..c327d3a --- /dev/null +++ b/TSE_Core/src/extern/miniaudio.c @@ -0,0 +1,2 @@ +#define MINIAUDIO_IMPLEMENTATION +#include "miniaudio.h" diff --git a/TSE_Core/src/interfaces/IWindow.cpp b/TSE_Core/src/interfaces/IWindow.cpp index 52a5948..ee3034e 100644 --- a/TSE_Core/src/interfaces/IWindow.cpp +++ b/TSE_Core/src/interfaces/IWindow.cpp @@ -4,6 +4,7 @@ #include "utils/Time.hpp" #include "version.h" #include "IInputManager.hpp" +#include "elements/AudioEngine.hpp" TSE::IWindow* TSE::IWindow::lastWindow = nullptr; @@ -12,6 +13,7 @@ bool TSE::IWindow::BaseInit() const LuaStateHandler::InitLuaState(); Debug::Init(); Debug::Log("TSE:" + TSE_VERSION_STRING); + AudioEngine::Init(); return true; } @@ -22,6 +24,7 @@ void TSE::IWindow::BaseUpdate() const TSE::IWindow::~IWindow() { + AudioEngine::Destroy(); IInputManager::instance()->Delete(); Time::Destroy(); Debug::Close(); diff --git a/TSE_Editor/src/EditorSubsystem.cpp b/TSE_Editor/src/EditorSubsystem.cpp index 9a5df0c..6cd96aa 100644 --- a/TSE_Editor/src/EditorSubsystem.cpp +++ b/TSE_Editor/src/EditorSubsystem.cpp @@ -1,6 +1,8 @@ #include "EditorSubsystem.hpp" #include "BehaviourScriptRegistry.hpp" +#include "BehaviourScripts/AudioListener.hpp" +#include "BehaviourScripts/AudioSource.hpp" TSE::EDITOR::EditorSubsystem::EditorSubsystem() : sv(nullptr), editorLayer("") { @@ -21,6 +23,8 @@ TSE::EDITOR::EditorSubsystem::EditorSubsystem() : sv(nullptr), editorLayer("") BehaviourScriptRegistry::RegisterBehaviourScript("Renderable", []() -> BehaviourScript* {return new Renderable();}); BehaviourScriptRegistry::RegisterBehaviourScript("Particle System", []() -> BehaviourScript* {return new ParticleSystem();}); BehaviourScriptRegistry::RegisterBehaviourScript("Camera", []() -> BehaviourScript* {return new Camera();}); + BehaviourScriptRegistry::RegisterBehaviourScript("Audio Listener", []() -> BehaviourScript* {return new AudioListener();}); + BehaviourScriptRegistry::RegisterBehaviourScript("Audio Source", []() -> BehaviourScript* {return new AudioSource();}); #pragma region camerastuff diff --git a/TSE_Editor/src/UI/ElementDrawer.cpp b/TSE_Editor/src/UI/ElementDrawer.cpp index 307cc15..b07c770 100644 --- a/TSE_Editor/src/UI/ElementDrawer.cpp +++ b/TSE_Editor/src/UI/ElementDrawer.cpp @@ -191,6 +191,10 @@ namespace TSE::EDITOR { Draw((RectBase*)element, debug); } + else if (name == "Audio Source") + { + Draw((AudioSource*)element, debug); + } else if (name == "Camera") { Draw((Camera*)element, debug); @@ -478,6 +482,78 @@ namespace TSE::EDITOR if(small) DrawImageAnimationSetCompact(element, debug, label); else DrawImageAnimationSetNormal(element, debug, label); } + void ElementDrawer::Draw(AudioSource *element, const bool &debug) + { + float availWidth = ImGui::GetContentRegionAvail().x; + int buttonCount = 3; + float spacing = ImGui::GetStyle().ItemSpacing.x; + float buttonWidth = (availWidth - (buttonCount - 1) * spacing) / buttonCount; + float buttonHeight = 0.0f; // 0.0 = default height + + if (ImGui::Button("Play", ImVec2(buttonWidth, buttonHeight))) + { + element->StartClip(element->currentlyPlaying, false); + } + ImGui::SameLine(); + if (ImGui::Button("Pause", ImVec2(buttonWidth, buttonHeight))) + { + element->PausePlaying(); + } + ImGui::SameLine(); + if (ImGui::Button("Stop", ImVec2(buttonWidth, buttonHeight))) + { + element->StopPlaying(); + } + ImGui::Separator(); + bool global = element->GetGlobal(); + if(ImGui::Checkbox("Global", &global)) + { + element->SetGlobal(global); + } + float minDistance = element->GetMinDistance(); + if(ImGui::DragFloat("Cutoff Distance", &minDistance)) + { + element->SetMinDistance(minDistance); + } + float maxDistance = element->GetMaxDistance(); + if(ImGui::DragFloat("Falloff Distance", &maxDistance)) + { + element->SetMaxDistance(maxDistance); + } + + BeginList("Audio Clips"); + //if(ListAddBtn()) { } // --> no add function for now maybe when asset manager is done + for(int i = 0; i < element->clips.size(); i++) + { + BeginListItem("subcomponent##" + std::to_string(i)); + Draw(element->GetClipAt(i), debug, "", true); + if (ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) + { + // Check auf Doppelclick mit linker Maustaste + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + { + element->StopPlaying(); + element->currentlyPlaying = element->GetClipAt(i)->name; + } + } + EndListItem(); + if(ListItemXBotton("x##" + std::to_string(i))) + { + element->RemoveClip(element->GetClipAt(i)->name); + } + } + EndList(); + } + void ElementDrawer::Draw(AudioClip *element, const bool &debug, const std::string &label, const bool small) + { + if(element == nullptr) + { + ImGui::Text("No Audio Clip Asigned"); + return; + } + if(small) DrawAudioClipCompact(element, debug, label); + else DrawAudioClipNormal(element, debug, label); + } void ElementDrawer::Draw(Camera *element, const bool &debug) { const bool isMain = (Camera::mainCamera == element); @@ -737,6 +813,39 @@ namespace TSE::EDITOR ImGui::Unindent(20.0f); } } + void ElementDrawer::DrawAudioClipCompact(AudioClip *element, const bool &debug, const std::string &label) + { + float item_spacing = ImGui::GetStyle().ItemSpacing.x; + ImVec2 label_size = ImGui::CalcTextSize(label.c_str()); + + float available_width = ImGui::GetContentRegionAvail().x; + float field_width = available_width - label_size.x - item_spacing; + + ImVec2 field_size = ImVec2(field_width, label_size.y + 4); + + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetColorU32(ImGuiCol_FrameBg)); // gleiche Farbe wie InputText + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, ImGui::GetStyle().FrameRounding); + + ImGui::BeginChild("##FakeInput", field_size, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + ImGui::SetCursorPos(ImVec2(2, 2)); + ImGui::TextUnformatted(element->name.c_str()); + + ImGui::EndChild(); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + + ImGui::SameLine(); + ImVec2 cursorCurrent = ImGui::GetCursorPos(); + cursorCurrent.y += 2; + ImGui::SetCursorPos(cursorCurrent); + ImGui::TextUnformatted(label.c_str()); + } + void ElementDrawer::DrawAudioClipNormal(AudioClip *element, const bool &debug, const std::string &label) + { + //TODO implement + } void ElementDrawer::DrawImageAnimationSetCompact(ImageAnimationSet *element, const bool &debug, const std::string &label) { float item_spacing = ImGui::GetStyle().ItemSpacing.x; diff --git a/TSE_Editor/src/UI/ElementDrawer.hpp b/TSE_Editor/src/UI/ElementDrawer.hpp index 52774e4..2acb17f 100644 --- a/TSE_Editor/src/UI/ElementDrawer.hpp +++ b/TSE_Editor/src/UI/ElementDrawer.hpp @@ -11,6 +11,8 @@ #include "BehaviourScripts/ImageAnimation.hpp" #include "BehaviourScripts/RectBase.hpp" #include "BehaviourScripts/ParticleSystem.hpp" +#include "BehaviourScripts/AudioListener.hpp" +#include "BehaviourScripts/AudioSource.hpp" namespace TSE::EDITOR { @@ -73,10 +75,13 @@ namespace TSE::EDITOR static void Draw(Sprite* element, const bool& debug, const std::string& label = "", const bool small = false); static void Draw(Mesh* element, const bool& debug, const std::string& label = "", const bool small = false); static void Draw(ImageAnimationSet* element, const bool& debug, const std::string& label = "", const bool small = false); + static void Draw(AudioSource* element, const bool& debug); + static void Draw(AudioClip* element, const bool& debug, const std::string& label = "", const bool small = false); static void Draw(Camera* element, const bool& debug); static void Draw(ParticleSystem* element, const bool& debug); - + static void DrawAudioClipCompact(AudioClip* element, const bool& debug, const std::string& label); + static void DrawAudioClipNormal(AudioClip* element, const bool& debug, const std::string& label); static void DrawImageAnimationSetCompact(ImageAnimationSet* element, const bool& debug, const std::string& label); static void DrawImageAnimationSetNormal(ImageAnimationSet* element, const bool& debug, const std::string& label); static void DrawMeshCompact(Mesh* element, const bool& debug, const std::string& label);