Files
khaotic-engine-Reborn/enginecustom/src/inc/system/ecs/components/audio_component.h
CatChow0 00339aa6c2 Patch - Improves code and UI for better engine stability - V14.5.30
Addresses minor issues across the engine to improve stability and UI.

- Updates ImGui window size for better rendering
- Adds macro for boolean returns (R_TRUE and R_FALSE)
- Adds missing includes and removes unused code
- Updates shader code to use the new macros and improve readability
2025-10-10 00:32:19 +02:00

552 lines
19 KiB
C++

// AudioComponent.h
#pragma once
#include "../component.h"
#include <Fmod/core/inc/fmod.hpp>
#include <string>
#include <filesystem>
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <Shlwapi.h>
#pragma comment(lib, "Shlwapi.lib")
#include <commdlg.h> // Pour GetOpenFileName
#include "camera_class.h"
#include "transform_component.h"
namespace ecs {
class AudioComponent : public Component {
public:
AudioComponent() : m_sound(nullptr), m_channel(nullptr) {}
~AudioComponent() override {
// stop the sound if it's playing
if (m_channel) {
bool isPlaying = false;
m_channel->isPlaying(&isPlaying);
if (isPlaying) {
m_channel->stop();
}
}
if (m_sound) m_sound->release();
// Ne pas libérer m_system ici car il peut être partagé
}
/**
* Shutdown the audio component by releasing the sound and channel.
* This method is called when the component is removed from an entity or when the entity is destroyed.
*/
void Shutdown() override {
if (m_channel) {
bool isPlaying = false;
m_channel->isPlaying(&isPlaying);
if (isPlaying) {
m_channel->stop();
}
m_channel = nullptr;
}
if (m_sound) {
m_sound->release();
m_sound = nullptr;
}
m_system = nullptr; // Ne pas libérer m_system ici car il peut être partagé
m_camera = nullptr;
}
/**
* Initialize the audio component by setting up the FMOD system and camera from the parent entity.
* This method is called when the component is added to an entity.
*/
void Initialize() override
{
auto parent = GetParent();
if (parent && parent->GetCamera())
{
SetCamera(parent->GetCamera());
SetSoundSystem(parent->GetSoundSystem());
}
}
/**
* Load an audio file from the specified path.
* If the FMOD system is not initialized, it will be initialized first.
* Creates a sound object with appropriate settings based on the component's properties.
* @param path The file path to load the audio from.
* @return True if the audio file was loaded successfully, otherwise false.
*/
bool Load(const std::string& path) {
if (!m_system) {
Initialize();
if (!m_system) R_FALSE
}
LOG_INFO("Loading audio file: " + path);
m_soundPath = path;
if (!std::filesystem::exists(path)) {
m_lastError = "Fichier non trouvé: " + path;
LOG_ERROR(m_lastError);
R_FALSE
}
if (m_sound) {
m_sound->release();
m_sound = nullptr;
}
FMOD_MODE mode = FMOD_DEFAULT;
if (m_spatialized)
mode |= FMOD_3D;
if (m_looping)
mode |= FMOD_LOOP_NORMAL;
else
mode |= FMOD_LOOP_OFF;
std::filesystem::path absolutePath = std::filesystem::absolute(path);
FMOD_RESULT result = m_system->createSound(absolutePath.string().c_str(), mode, nullptr, &m_sound);
if (result != FMOD_OK) {
m_lastError = "Échec du chargement du son: " + std::to_string(result) +
" (chemin: " + absolutePath.string() + ")";
LOG_ERROR(m_lastError);
R_FALSE
}
R_TRUE
}
/**
* Play the loaded audio sound.
* If the sound is already playing, it will be stopped and restarted.
* The channel properties such as volume, pan, pitch, mute, priority, and paused state are applied.
*/
void Play() {
if (m_system && m_sound) {
bool isPlaying = false;
if (m_channel) {
m_channel->isPlaying(&isPlaying);
}
if (isPlaying) {
m_channel->stop();
}
m_system->playSound(m_sound, nullptr, false, &m_channel);
if (m_channel) {
m_channel->setVolume(m_volume);
m_channel->setPan(m_pan);
m_channel->setPitch(m_pitch);
m_channel->setMute(m_muted);
m_channel->setPriority(m_priority);
m_channel->setPaused(m_paused);
}
}
}
/**
* Pause the currently playing audio sound.
* If the sound is not playing, this method has no effect.
*/
void Pause() {
if (m_channel) {
m_paused = true;
m_channel->setPaused(true);
}
}
/**
* Resume the paused audio sound.
* If the sound is not paused, this method has no effect.
*/
void Resume() {
if (m_channel) {
m_paused = false;
m_channel->setPaused(false);
}
}
/**
* Stop the currently playing audio sound.
* If the sound is not playing, this method has no effect.
*/
void Stop() {
if (m_channel)
m_channel->stop();
}
/**
* Set the camera to be used for 3D audio spatialization.
* This camera is used to update the listener attributes in FMOD.
* @param camera Pointer to the camera_class instance.
*/
void SetCamera(camera_class* camera) {m_camera = camera; }
/**
* Update the audio component.
* @param deltaTime Time since the last update.
*/
void Update(float deltaTime) override {
if (!m_spatialized) return;
if (!m_system || !m_camera) return;
auto parent = GetParent();
if (!parent) return;
auto transform = parent->GetComponent<TransformComponent>();
if (!transform) return;
XMVECTOR pos = transform->GetPosition();
m_position.x = XMVectorGetX(pos);
m_position.y = XMVectorGetY(pos);
m_position.z = XMVectorGetZ(pos);
if (m_channel) {
FMOD_VECTOR velocity = { 0.f, 0.f, 0.f }; // à améliorer si possible - calculer depuis déplacement temps+position ?
m_channel->set3DAttributes(&m_position, &velocity);
}
// Mise à jour du listener FMOD
XMFLOAT3 camPos = m_camera->get_position();
XMFLOAT3 camForward = m_camera->get_forward();
XMFLOAT3 camUp = m_camera->get_up();
FMOD_VECTOR listenerPos = { camPos.x, camPos.y, camPos.z };
FMOD_VECTOR forward = { camForward.x, camForward.y, camForward.z };
FMOD_VECTOR up = { camUp.x, camUp.y, camUp.z };
FMOD_VECTOR listenerVel = { 0.f, 0.f, 0.f };
if (m_use_velocity)
{
static XMFLOAT3 lastCamPos = camPos;
XMFLOAT3 currentCamPos = camPos;
float deltaTimeSec = deltaTime > 0.0001f ? deltaTime : 0.016f; // éviter division par zéro
XMFLOAT3 listenerVelocity = {
(currentCamPos.x - lastCamPos.x) / deltaTimeSec,
(currentCamPos.y - lastCamPos.y) / deltaTimeSec,
(currentCamPos.z - lastCamPos.z) / deltaTimeSec
};
lastCamPos = currentCamPos;
listenerVel = { listenerVelocity.x, listenerVelocity.y, listenerVelocity.z };
}
m_system->set3DListenerAttributes(0, &listenerPos, &listenerVel, &forward, &up);
m_system->update();
}
/**
* ImGui Widget to control the audio component properties.
* Allows loading/changing/removing audio files, and adjusting properties like volume, pan, pitch, looping, spatialization, etc.
* Displays error messages if loading fails.
* This method is called during the ImGui rendering phase.
*/
void OnImGuiRender() override {
if (!m_sound) {
ImGui::Text("No audio file loaded");
if (ImGui::Button("Load audio file")) {
OPENFILENAME ofn;
wchar_t szFile[MAX_PATH] = L"";
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = NULL; // Renseigner si possible avec handle de la fenêtre
ofn.lpstrFile = szFile;
ofn.nMaxFile = MAX_PATH;
ofn.lpstrFilter = L"Audio Files\0*.mp3;*.wav;*.ogg;*.flac\0All Files\0*.*\0";
ofn.nFilterIndex = 1;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
if (GetOpenFileName(&ofn)) {
wchar_t exePath[MAX_PATH];
GetModuleFileName(NULL, exePath, MAX_PATH);
PathRemoveFileSpec(exePath);
std::wstring targetDir = std::wstring(exePath) + L"\\assets\\sounds";
DWORD ftyp = GetFileAttributes(targetDir.c_str());
if (ftyp == INVALID_FILE_ATTRIBUTES) {
CreateDirectory((std::wstring(exePath) + L"\\assets").c_str(), NULL);
CreateDirectory(targetDir.c_str(), NULL);
}
const wchar_t* filename = wcsrchr(szFile, L'\\');
std::wstring fileNameStr = filename ? (filename + 1) : szFile;
std::wstring targetPath = targetDir + L"\\" + fileNameStr;
if (!CopyFile(szFile, targetPath.c_str(), FALSE)) {
MessageBox(NULL, L"Erreur lors de la copie du fichier audio.", L"Erreur", MB_OK | MB_ICONERROR);
} else {
int utf8size = WideCharToMultiByte(CP_UTF8, 0, targetPath.c_str(), -1, NULL, 0, NULL, NULL);
std::string pathStr(utf8size, 0);
WideCharToMultiByte(CP_UTF8, 0, targetPath.c_str(), -1, &pathStr[0], utf8size, NULL, NULL);
pathStr.resize(utf8size - 1);
if (!Load(pathStr)) {
// erreur dans m_lastError
}
}
}
}
if (!m_lastError.empty()) {
ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error: %s", m_lastError.c_str());
LOG_ERROR(m_lastError);
}
} else {
ImGui::Text("Loaded: %s", m_soundPath.c_str());
if (ImGui::Button("Change audio")) {
OPENFILENAME ofn;
wchar_t szFile[MAX_PATH] = L"";
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = NULL;
ofn.lpstrFile = szFile;
ofn.nMaxFile = MAX_PATH;
ofn.lpstrFilter = L"Audio Files\0*.mp3;*.wav;*.ogg;*.flac\0All Files\0*.*\0";
ofn.nFilterIndex = 1;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
if (GetOpenFileName(&ofn)) {
wchar_t exePath[MAX_PATH];
GetModuleFileName(NULL, exePath, MAX_PATH);
PathRemoveFileSpec(exePath);
std::wstring targetDir = std::wstring(exePath) + L"\\assets\\sounds";
DWORD ftyp = GetFileAttributes(targetDir.c_str());
if (ftyp == INVALID_FILE_ATTRIBUTES) {
CreateDirectory((std::wstring(exePath) + L"\\assets").c_str(), NULL);
CreateDirectory(targetDir.c_str(), NULL);
}
const wchar_t* filename = wcsrchr(szFile, L'\\');
std::wstring fileNameStr = filename ? (filename + 1) : szFile;
std::wstring targetPath = targetDir + L"\\" + fileNameStr;
if (!CopyFile(szFile, targetPath.c_str(), FALSE)) {
MessageBox(NULL, L"Erreur lors de la copie du fichier audio.", L"Erreur", MB_OK | MB_ICONERROR);
} else {
int utf8size = WideCharToMultiByte(CP_UTF8, 0, targetPath.c_str(), -1, NULL, 0, NULL, NULL);
std::string pathStr(utf8size, 0);
WideCharToMultiByte(CP_UTF8, 0, targetPath.c_str(), -1, &pathStr[0], utf8size, NULL, NULL);
pathStr.resize(utf8size - 1);
if (!Load(pathStr)) {
// erreur dans m_lastError
}
}
}
}
ImGui::SameLine();
if (ImGui::Button("Remove audio")) {
Stop();
if (m_sound) m_sound->release();
m_sound = nullptr;
m_soundPath.clear();
}
ImGui::Separator();
if (ImGui::Button("Play")) {
Play();
}
ImGui::SameLine();
if (ImGui::Button(m_paused ? "Resume" : "Pause")) {
if (m_paused) Resume();
else Pause();
}
ImGui::SameLine();
if (ImGui::Button("Stop")) {
Stop();
}
ImGui::Separator();
if (ImGui::SliderFloat("Volume", &m_volume, 0.0f, 1.0f)) {
if (m_channel) m_channel->setVolume(m_volume);
}
if (ImGui::SliderFloat("Pan", &m_pan, -1.0f, 1.0f)) {
if (m_channel) m_channel->setPan(m_pan);
}
if (ImGui::SliderFloat("Pitch", &m_pitch, 0.5f, 2.0f)) {
if (m_channel) m_channel->setPitch(m_pitch);
}
if (ImGui::Checkbox("Looping", &m_looping)) {
// Recharger le son pour appliquer le mode loop
Load(m_soundPath);
}
if (ImGui::Checkbox("Spatialization 3D", &m_spatialized)) {
if (!m_soundPath.empty()) {
Load(m_soundPath); // Recharger le son avec ou sans 3D selon le choix
}
}
// check box to use velocity for doppler effect (only if spatialized)
if (m_spatialized) {
ImGui::SameLine();
ImGui::Checkbox("Use Velocity for Doppler", &m_use_velocity);
}
if (ImGui::Checkbox("Mute", &m_muted)) {
if (m_channel) m_channel->setMute(m_muted);
}
if (ImGui::SliderInt("Priority", &m_priority, 0, 256)) {
if (m_channel) m_channel->setPriority(m_priority);
}
ImGui::Separator();
if (m_channel && m_sound) {
unsigned int pos = 0, len = 0;
m_channel->getPosition(&pos, FMOD_TIMEUNIT_MS);
m_sound->getLength(&len, FMOD_TIMEUNIT_MS);
float progress = (len > 0) ? (float)pos / len : 0.0f;
ImGui::ProgressBar(progress, ImVec2(300, 0), nullptr);
auto to_minsec = [](unsigned int ms) {
unsigned int totalSec = ms / 1000;
return std::make_pair(totalSec / 60, totalSec % 60);
};
auto [curMin, curSec] = to_minsec(pos);
auto [lenMin, lenSec] = to_minsec(len);
ImGui::Text("%02u:%02u / %02u:%02u", curMin, curSec, lenMin, lenSec);
}
// afficher la position de l'objet et de la camera ainsi que la rotaion de la camera si spatialisé
if (m_spatialized) {
auto parent = GetParent();
if (parent) {
auto transform = parent->GetComponent<TransformComponent>();
if (transform) {
XMVECTOR pos = transform->GetPosition();
ImGui::Text("Object Position: (%.2f, %.2f, %.2f)", XMVectorGetX(pos), XMVectorGetY(pos), XMVectorGetZ(pos));
}
}
if (m_camera) {
XMFLOAT3 camPos = m_camera->get_position();
XMFLOAT3 camRot = m_camera->get_rotation();
ImGui::Text("Camera Position: (%.2f, %.2f, %.2f)", camPos.x, camPos.y, camPos.z);
ImGui::Text("Camera Rotation: (%.2f, %.2f, %.2f)", camRot.x, camRot.y, camRot.z);
}
}
if (!m_lastError.empty()) {
ImGui::TextColored(ImVec4(1,0,0,1), "Error: %s", m_lastError.c_str());
}
}
}
/**
* Set the FMOD system to be used by this audio component.
* This method allows sharing the FMOD system instance across multiple audio components.
* @param system Pointer to the FMOD::System instance.
*/
void SetSoundSystem(FMOD::System* system) {
m_system = system;
}
/**
* Serialize the audio component's state to a string.
* This includes properties like sound path, volume, pan, pitch, looping, muted, paused, priority, spatialization, and use of velocity.
* This method is useful for saving the component's state or debugging.
* @return A string representation of the audio component's state.
*/
std::string Serialize() const override {
std::stringstream ss;
ss << "AudioComponent:"
<< m_soundPath << ";"
<< m_volume << ":"
<< m_pan << ":"
<< m_pitch << ":"
<< m_looping << ":"
<< m_muted << ":"
<< m_paused << ":"
<< m_priority << ":"
<< m_spatialized << ":"
<< m_use_velocity;
return ss.str();
}
/**
* Deserialize the audio component's state from a string.
* This method parses the string and sets the component's properties accordingly.
* If the sound path is valid, it attempts to load the audio file.
* @param data The string representation of the audio component's state.
* @return True if deserialization was successful, otherwise false.
*/
bool Deserialize(const std::string& data) override {
std::stringstream ss(data);
std::string type;
std::getline(ss, type, ':');
if (type != "AudioComponent") R_FALSE
std::string s_volume, s_pan, s_pitch, s_looping, s_muted, s_paused, s_priority, s_spatialized, s_use_velocity;
std::getline(ss, m_soundPath, ';');
std::getline(ss, s_volume, ':');
std::getline(ss, s_pan, ':');
std::getline(ss, s_pitch, ':');
std::getline(ss, s_looping, ':');
std::getline(ss, s_muted, ':');
std::getline(ss, s_paused, ':');
std::getline(ss, s_priority, ':');
std::getline(ss, s_spatialized, ':');
std::getline(ss, s_use_velocity, ':');
m_volume = std::stof(s_volume);
m_pan = std::stof(s_pan);
m_pitch = std::stof(s_pitch);
m_looping = (s_looping == "1");
m_muted = (s_muted == "1");
m_paused = (s_paused == "1");
m_priority = std::stoi(s_priority);
m_spatialized = (s_spatialized == "1");
m_use_velocity = (s_use_velocity == "1");
// Recharger le son si le chemin existe (optional: ou laisser le chargement manuel)
if (!m_soundPath.empty() && m_system) {
Load(m_soundPath);
}
R_TRUE
}
private:
FMOD::System* m_system;
FMOD::Sound* m_sound;
FMOD::Channel* m_channel;
std::string m_soundPath;
std::string m_lastError;
float m_volume = 1.0f;
float m_pan = 0.0f;
float m_pitch = 1.0f;
bool m_looping = false;
bool m_muted = false;
bool m_paused = false;
int m_priority = 128;
bool m_spatialized = false;
bool m_use_velocity = false;
FMOD_VECTOR m_position = {0.0f, 0.0f, 0.0f};
camera_class* m_camera = nullptr;
};
}