Minor - Adds audio component with spatialization - V13.3.0
Adds a new audio component with support for loading, playing, pausing, stopping, and controlling audio properties such as volume, pan, pitch, and looping. Implements spatialization using FMOD, enabling 3D audio effects based on object and camera positions. Includes file selection dialog and UI controls for audio properties.
This commit is contained in:
@@ -4,6 +4,14 @@
|
||||
#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 {
|
||||
@@ -25,38 +33,51 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
result = m_system->init(512, FMOD_INIT_NORMAL, nullptr);
|
||||
result = m_system->init(512, FMOD_INIT_NORMAL | FMOD_INIT_3D_RIGHTHANDED, nullptr);
|
||||
if (result != FMOD_OK) {
|
||||
m_lastError = "<EFBFBD>chec de l'initialisation du syst<73>me FMOD: " + std::to_string(result);
|
||||
m_system->release();
|
||||
m_system = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto parent = GetParent();
|
||||
if (parent && parent->GetCamera())
|
||||
{
|
||||
SetCamera(parent->GetCamera());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool Load(const std::string& path) {
|
||||
if (!m_system) {
|
||||
Initialize();
|
||||
if (!m_system) return false; // L'erreur est d<>j<EFBFBD> d<>finie dans Initialize()
|
||||
if (!m_system) return false;
|
||||
}
|
||||
|
||||
m_soundPath = path;
|
||||
|
||||
// V<>rifier si le fichier existe
|
||||
if (!std::filesystem::exists(path)) {
|
||||
m_lastError = "Fichier non trouv<75>: " + path;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Lib<69>rer le son pr<70>c<EFBFBD>dent s'il existe
|
||||
if (m_sound) {
|
||||
m_sound->release();
|
||||
m_sound = nullptr;
|
||||
}
|
||||
|
||||
// Essayer de charger avec le chemin absolu
|
||||
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(), FMOD_DEFAULT, nullptr, &m_sound);
|
||||
|
||||
FMOD_RESULT result = m_system->createSound(absolutePath.string().c_str(), mode, nullptr, &m_sound);
|
||||
if (result != FMOD_OK) {
|
||||
m_lastError = "<EFBFBD>chec du chargement du son: " + std::to_string(result) +
|
||||
" (chemin: " + absolutePath.string() + ")";
|
||||
@@ -67,8 +88,41 @@ public:
|
||||
}
|
||||
|
||||
void Play() {
|
||||
if (m_system && m_sound)
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Pause() {
|
||||
if (m_channel) {
|
||||
m_paused = true;
|
||||
m_channel->setPaused(true);
|
||||
}
|
||||
}
|
||||
|
||||
void Resume() {
|
||||
if (m_channel) {
|
||||
m_paused = false;
|
||||
m_channel->setPaused(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
@@ -76,49 +130,288 @@ public:
|
||||
m_channel->stop();
|
||||
}
|
||||
|
||||
void OnImGuiRender() override {
|
||||
// Afficher le r<>pertoire de travail actuel
|
||||
std::string currentDir = std::filesystem::current_path().string();
|
||||
ImGui::Text("R<EFBFBD>pertoire actuel: %s", currentDir.c_str());
|
||||
void SetCamera(camera_class* camera) {m_camera = camera; }
|
||||
|
||||
if (m_sound) {
|
||||
ImGui::Text("Son charg<72>: %s", m_soundPath.c_str());
|
||||
if (ImGui::Button("Jouer")) {
|
||||
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 }; // <20> am<61>liorer si possible - calculer depuis d<>placement temps+position ?
|
||||
m_channel->set3DAttributes(&m_position, &velocity);
|
||||
}
|
||||
|
||||
// Mise <20> 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; // <20>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();
|
||||
}
|
||||
|
||||
|
||||
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<65>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("Arr<EFBFBD>ter")) {
|
||||
if (ImGui::Button(m_paused ? "Resume" : "Pause")) {
|
||||
if (m_paused) Resume();
|
||||
else Pause();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Stop")) {
|
||||
Stop();
|
||||
}
|
||||
} else {
|
||||
ImGui::Text("Aucun son charg<72>");
|
||||
|
||||
// Montrer l'erreur s'il y en a une
|
||||
if (!m_lastError.empty()) {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Erreur: %s", m_lastError.c_str());
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::SliderFloat("Volume", &m_volume, 0.0f, 1.0f)) {
|
||||
if (m_channel) m_channel->setVolume(m_volume);
|
||||
}
|
||||
|
||||
// exe path + assets/sounds/default.mp3
|
||||
std::filesystem::path defaultSoundPath = std::filesystem::current_path() / "assets" / "sounds" / "default.mp3";
|
||||
std::string defaultSoundPathStr = defaultSoundPath.string();// Input text with default path
|
||||
char path[260];
|
||||
strncpy_s(path, m_soundPath.empty() ? defaultSoundPathStr.c_str() : m_soundPath.c_str(), sizeof(path));
|
||||
path[sizeof(path) - 1] = '\0'; // Assurer la termination nulle
|
||||
ImGui::InputText("Chemin du fichier", path, sizeof(path));
|
||||
|
||||
if (ImGui::Button("Charger le son")) {
|
||||
if (!Load(path)) {
|
||||
// L'erreur est d<>j<EFBFBD> d<>finie dans Load()
|
||||
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<69>
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user