// AudioComponent.h #pragma once #include "../component.h" #include #include #include #define WIN32_LEAN_AND_MEAN #include #include #pragma comment(lib, "Shlwapi.lib") #include // 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 { if (m_sound) m_sound->release(); // Ne pas libérer m_system ici car il peut être partagé } /** * 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) return false; } m_soundPath = path; if (!std::filesystem::exists(path)) { m_lastError = "Fichier non trouvé: " + path; return 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() + ")"; return false; } return 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(); 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()); } } 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(); 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") return 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); } return 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; }; }