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
552 lines
19 KiB
C++
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;
|
|
};
|
|
}
|