diff --git a/cmake/FetchDependencies.cmake b/cmake/FetchDependencies.cmake index 24903fc..b165cff 100644 --- a/cmake/FetchDependencies.cmake +++ b/cmake/FetchDependencies.cmake @@ -53,3 +53,6 @@ check_and_download("https://github.com/lua/lua/archive/refs/tags/v5.4.8.zip" "lu # 10) sol2 (header-only C++ bindings for Lua) check_and_download("https://github.com/ThePhD/sol2/archive/refs/tags/v3.3.0.zip" "sol2-v3.3.0.zip" "sol2-3.3.0" "include/sol/sol.hpp") + +# 11) SDL2_mixer +check_and_download("https://github.com/libsdl-org/SDL_mixer/archive/refs/tags/release-2.8.0.zip" "SDL_mixer-release-2.8.0.zip" "SDL_mixer-release-2.8.0" "CMakeLists.txt") diff --git a/cmake/ThirdParty.cmake b/cmake/ThirdParty.cmake index 8e940c3..4b6d015 100644 --- a/cmake/ThirdParty.cmake +++ b/cmake/ThirdParty.cmake @@ -607,4 +607,122 @@ if(NOT TARGET sol2_external_lib) add_library(sol2_external_lib INTERFACE) target_include_directories(sol2_external_lib INTERFACE "${SOL2_SRC_DIR}/include") target_link_libraries(sol2_external_lib INTERFACE lua_static) -endif() \ No newline at end of file +endif() + +# =========================================== +# 11) SDL2_mixer (2.8.0) – сборка из исходников +# =========================================== +set(SDL2MIXER_SRC_DIR "${THIRDPARTY_DIR}/SDL_mixer-release-2.8.0") +set(SDL2MIXER_BASE_DIR "${SDL2MIXER_SRC_DIR}/install") +set(SDL2MIXER_BASE_DIR "${SDL2MIXER_BASE_DIR}" CACHE PATH "SDL2_mixer install base directory" FORCE) + +set(_have_sdl2mixer TRUE) +foreach(cfg IN LISTS BUILD_CONFIGS) + if(NOT EXISTS "${SDL2MIXER_BASE_DIR}-${cfg}/lib/SDL2_mixer.lib" AND + NOT EXISTS "${SDL2MIXER_BASE_DIR}-${cfg}/lib/SDL2_mixerd.lib") + set(_have_sdl2mixer FALSE) + endif() +endforeach() + +if(NOT _have_sdl2mixer) + foreach(cfg IN LISTS BUILD_CONFIGS) + if(cfg STREQUAL "Debug") + set(_SDL2_LIB "${SDL2_INSTALL_DIR}/lib/SDL2d.lib") + else() + set(_SDL2_LIB "${SDL2_INSTALL_DIR}/lib/SDL2.lib") + endif() + + log("Configuring SDL2_mixer (${cfg}) ...") + execute_process( + COMMAND ${CMAKE_COMMAND} + -G "${CMAKE_GENERATOR}" + -S "${SDL2MIXER_SRC_DIR}" + -B "${SDL2MIXER_SRC_DIR}/build-${cfg}" + -DCMAKE_INSTALL_PREFIX=${SDL2MIXER_BASE_DIR}-${cfg} + -DCMAKE_PREFIX_PATH=${SDL2_INSTALL_DIR} + -DSDL2_LIBRARY=${_SDL2_LIB} + -DSDL2_INCLUDE_DIR=${SDL2_INSTALL_DIR}/include/SDL2 + -DSDL2MIXER_DEPS_SHARED=OFF + -DSDL2MIXER_VENDORED=ON + -DSDL2MIXER_SAMPLES=OFF + -DSDL2MIXER_MUSIC_CMD=OFF + -DSDL2MIXER_MOD=OFF + -DSDL2MIXER_MIDI=OFF + -DSDL2MIXER_OPUS=OFF + -DSDL2MIXER_WAVPACK=OFF + -DSDL2MIXER_MP3_MPG123=OFF + -DSDL2MIXER_MP3_DRMP3=ON + -DSDL2MIXER_FLAC_DRFLAC=ON + -DSDL2MIXER_OGG_STB=ON + -DCMAKE_DISABLE_FIND_PACKAGE_OGG=TRUE + -DCMAKE_DISABLE_FIND_PACKAGE_Vorbis=TRUE + -DCMAKE_DISABLE_FIND_PACKAGE_FLAC=TRUE + -DCMAKE_DISABLE_FIND_PACKAGE_MPG123=TRUE + -DCMAKE_DISABLE_FIND_PACKAGE_LibModPlug=TRUE + -DCMAKE_DISABLE_FIND_PACKAGE_FluidLite=TRUE + RESULT_VARIABLE _mixer_cfg_res + OUTPUT_VARIABLE _mixer_cfg_out + ERROR_VARIABLE _mixer_cfg_err + ) + if(NOT _mixer_cfg_res EQUAL 0) + message(STATUS "SDL2_mixer configure stdout: ${_mixer_cfg_out}") + message(STATUS "SDL2_mixer configure stderr: ${_mixer_cfg_err}") + message(FATAL_ERROR "SDL2_mixer configure failed for ${cfg}") + endif() + + log("Building SDL2_mixer (${cfg}) ...") + execute_process( + COMMAND ${CMAKE_COMMAND} + --build "${SDL2MIXER_SRC_DIR}/build-${cfg}" --config ${cfg} + RESULT_VARIABLE _mixer_build_res + ) + if(NOT _mixer_build_res EQUAL 0) + message(FATAL_ERROR "SDL2_mixer build failed for ${cfg}") + endif() + + log("Installing SDL2_mixer (${cfg}) ...") + execute_process( + COMMAND ${CMAKE_COMMAND} + --install "${SDL2MIXER_SRC_DIR}/build-${cfg}" --config ${cfg} + RESULT_VARIABLE _mixer_inst_res + ) + if(NOT _mixer_inst_res EQUAL 0) + message(FATAL_ERROR "SDL2_mixer install failed for ${cfg}") + endif() + endforeach() +endif() + +set(_mixer_debug_lib "") +foreach(cand + "${SDL2MIXER_BASE_DIR}-Debug/lib/SDL2_mixerd.lib" + "${SDL2MIXER_BASE_DIR}-Debug/lib/SDL2_mixer.lib" +) + if(EXISTS "${cand}") + set(_mixer_debug_lib "${cand}") + break() + endif() +endforeach() + +set(_mixer_release_lib "") +foreach(cand + "${SDL2MIXER_BASE_DIR}-Release/lib/SDL2_mixer.lib" +) + if(EXISTS "${cand}") + set(_mixer_release_lib "${cand}") + break() + endif() +endforeach() + +if(_mixer_debug_lib STREQUAL "" OR _mixer_release_lib STREQUAL "") + message(FATAL_ERROR "SDL2_mixer libs not found in ${SDL2MIXER_BASE_DIR}-Debug/Release") +endif() + +add_library(SDL2_mixer_external_lib UNKNOWN IMPORTED GLOBAL) +set_target_properties(SDL2_mixer_external_lib PROPERTIES + IMPORTED_LOCATION_DEBUG "${_mixer_debug_lib}" + IMPORTED_LOCATION_RELEASE "${_mixer_release_lib}" + INTERFACE_INCLUDE_DIRECTORIES + "$,${SDL2MIXER_BASE_DIR}-Debug/include,${SDL2MIXER_BASE_DIR}-Release/include>" + INTERFACE_LINK_LIBRARIES + "SDL2_external_lib" +) \ No newline at end of file diff --git a/proj-windows/CMakeLists.txt b/proj-windows/CMakeLists.txt index 5bcf89b..cdfb367 100644 --- a/proj-windows/CMakeLists.txt +++ b/proj-windows/CMakeLists.txt @@ -112,6 +112,7 @@ target_link_libraries(space-game001 PRIVATE eigen_external_lib boost_external_lib sol2_external_lib + SDL2_mixer_external_lib ) # Линкуем OpenGL (Windows) @@ -135,7 +136,8 @@ if (WIN32) set(ZLIB_DLL_DST "$,$/zd.dll,$/z.dll>") set(SDL2TTF_DLL_SRC "$,${SDL2TTF_BASE_DIR}-Debug/bin/SDL2_ttfd.dll,${SDL2TTF_BASE_DIR}-Release/bin/SDL2_ttf.dll>") - + + set(SDL2MIXER_DLL_SRC "$,${SDL2MIXER_BASE_DIR}-Debug/bin/SDL2_mixerd.dll,${SDL2MIXER_BASE_DIR}-Release/bin/SDL2_mixer.dll>") add_custom_command(TARGET space-game001 POST_BUILD COMMAND ${CMAKE_COMMAND} -E echo "Copying DLLs to output folder..." @@ -157,6 +159,10 @@ if (WIN32) COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SDL2TTF_DLL_SRC}" "$" + + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/SDL_mixer-release-2.8.0/install-$/bin/SDL2_mixer$<$:d>.dll" + "$/SDL2_mixer$<$:d>.dll" ) endif() diff --git a/resources/sounds/background.wav b/resources/sounds/background.wav new file mode 100644 index 0000000..dfe4693 Binary files /dev/null and b/resources/sounds/background.wav differ diff --git a/resources/sounds/lullaby-music-vol20-186394--online-audio-convert.com.ogg b/resources/sounds/lullaby-music-vol20-186394--online-audio-convert.com.ogg new file mode 100644 index 0000000..f22a04e Binary files /dev/null and b/resources/sounds/lullaby-music-vol20-186394--online-audio-convert.com.ogg differ diff --git a/src/AudioPlayerAsync.cpp b/src/AudioPlayerAsync.cpp index 879e82a..d738ba3 100644 --- a/src/AudioPlayerAsync.cpp +++ b/src/AudioPlayerAsync.cpp @@ -1,7 +1,5 @@ -#ifdef AUDIO - -#include "AudioPlayerAsync.h" - +#include "AudioPlayerAsync.h" +#include AudioPlayerAsync::AudioPlayerAsync() : worker(&AudioPlayerAsync::workerThread, this) {} @@ -11,62 +9,144 @@ AudioPlayerAsync::~AudioPlayerAsync() { stop = true; cv.notify_all(); } - worker.join(); + if (worker.joinable()) + worker.join(); + shutdown(); } -void AudioPlayerAsync::stopAsync() { - std::unique_lock lock(mtx); - taskQueue.push([this]() { - //audioPlayerMutex.lock(); - audioPlayer->stop(); - std::this_thread::sleep_for(std::chrono::seconds(1)); - //audioPlayerMutex.unlock(); - }); - cv.notify_one(); +bool AudioPlayerAsync::init() { + if (initialized) return true; + + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + std::cerr << "SDL_InitSubSystem(SDL_INIT_AUDIO) failed: " << SDL_GetError() << std::endl; + return false; + } + + if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) { + std::cerr << "Mix_OpenAudio failed: " << Mix_GetError() << std::endl; + return false; + } + + Mix_AllocateChannels(16); + + initialized = true; + std::cout << "AudioPlayerAsync initialized with SDL2_mixer" << std::endl; + return true; } -void AudioPlayerAsync::resetAsync() { - std::unique_lock lock(mtx); - taskQueue.push([this]() { - //audioPlayerMutex.lock(); - audioPlayer.reset(); - audioPlayer = std::make_unique(); - - //audioPlayerMutex.unlock(); - }); - cv.notify_one(); -} +void AudioPlayerAsync::shutdown() { + if (!initialized) return; -void AudioPlayerAsync::playSoundAsync(std::string soundName) { - - soundNameMutex.lock(); - latestSoundName = soundName; - soundNameMutex.unlock(); - - std::unique_lock lock(mtx); - taskQueue.push([this]() { - //audioPlayerMutex.lock(); - if (audioPlayer) { - audioPlayer->playSound(latestSoundName); + { + std::lock_guard lock(soundCacheMutex); + for (auto& pair : soundCache) { + Mix_FreeChunk(pair.second); + } + soundCache.clear(); + } + + Mix_CloseAudio(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + initialized = false; + std::cout << "AudioPlayerAsync shutdown" << std::endl; +} + +void AudioPlayerAsync::playSoundAsync(const std::string& filePath, int loops, int channel) { + if (!initialized) { + std::cerr << "AudioPlayerAsync not initialized" << std::endl; + return; + } + + std::unique_lock lock(mtx); + taskQueue.push([this, filePath, loops, channel]() { + Mix_Chunk* sound = nullptr; + { + std::lock_guard cacheLock(soundCacheMutex); + auto it = soundCache.find(filePath); + if (it != soundCache.end()) { + sound = it->second; + } + } + + if (!sound) { + sound = Mix_LoadWAV(filePath.c_str()); + if (!sound) { + std::cerr << "Failed to load sound " << filePath << ": " << Mix_GetError() << std::endl; + return; + } + std::lock_guard cacheLock(soundCacheMutex); + soundCache[filePath] = sound; + } + + int result = Mix_PlayChannel(channel, sound, loops); + if (result == -1) { + std::cerr << "Mix_PlayChannel failed: " << Mix_GetError() << std::endl; } - //audioPlayerMutex.unlock(); }); cv.notify_one(); } -void AudioPlayerAsync::playMusicAsync(std::string musicName) { - - musicNameMutex.lock(); - latestMusicName = musicName; - musicNameMutex.unlock(); +void AudioPlayerAsync::playMusicAsync(const std::string& filePath, int loops) { + if (!initialized) return; std::unique_lock lock(mtx); - taskQueue.push([this]() { - //audioPlayerMutex.lock(); - if (audioPlayer) { - audioPlayer->playMusic(latestMusicName); + taskQueue.push([this, filePath, loops]() { + Mix_Music* music = Mix_LoadMUS(filePath.c_str()); + if (!music) { + std::cerr << "Failed to load music " << filePath << ": " << Mix_GetError() << std::endl; + return; } - //audioPlayerMutex.unlock(); + if (Mix_PlayMusic(music, loops) == -1) { + std::cerr << "Mix_PlayMusic failed: " << Mix_GetError() << std::endl; + Mix_FreeMusic(music); + } + }); + cv.notify_one(); +} + +void AudioPlayerAsync::stopMusicAsync() { + if (!initialized) return; + std::unique_lock lock(mtx); + taskQueue.push([]() { + Mix_HaltMusic(); + }); + cv.notify_one(); +} + +void AudioPlayerAsync::pauseMusicAsync() { + if (!initialized) return; + std::unique_lock lock(mtx); + taskQueue.push([]() { + Mix_PauseMusic(); + }); + cv.notify_one(); +} + +void AudioPlayerAsync::resumeMusicAsync() { + if (!initialized) return; + std::unique_lock lock(mtx); + taskQueue.push([]() { + Mix_ResumeMusic(); + }); + cv.notify_one(); +} + +void AudioPlayerAsync::setMusicVolume(int volume) { + if (!initialized) return; + volume = std::max(0, std::min(128, volume)); + std::unique_lock lock(mtx); + taskQueue.push([volume]() { + Mix_VolumeMusic(volume); + }); + cv.notify_one(); +} + +void AudioPlayerAsync::setSoundVolume(int volume) { + if (!initialized) return; + volume = std::max(0, std::min(128, volume)); + std::unique_lock lock(mtx); + taskQueue.push([volume]() { + Mix_Volume(-1, volume); // все каналы }); cv.notify_one(); } @@ -76,18 +156,12 @@ void AudioPlayerAsync::workerThread() { std::function task; { std::unique_lock lock(mtx); - cv.wait(lock, [this]() { return !taskQueue.empty() || stop; }); - - if (stop && taskQueue.empty()) { + cv.wait(lock, [this] { return !taskQueue.empty() || stop; }); + if (stop && taskQueue.empty()) break; - } - - task = taskQueue.front(); + task = std::move(taskQueue.front()); taskQueue.pop(); } - task(); } -} - -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/src/AudioPlayerAsync.h b/src/AudioPlayerAsync.h index 131d6ea..4f97c8f 100644 --- a/src/AudioPlayerAsync.h +++ b/src/AudioPlayerAsync.h @@ -1,53 +1,45 @@ #pragma once -#ifdef AUDIO - -#include -#include +#include +#include +#include +#include #include -#include #include +#include +#include #include -#include "cmakeaudioplayer/include/AudioPlayer.hpp" - +#include class AudioPlayerAsync { public: AudioPlayerAsync(); ~AudioPlayerAsync(); - void resetAsync(); + bool init(); + void shutdown(); - void playSoundAsync(std::string soundName); + void playSoundAsync(const std::string& filePath, int loops = 0, int channel = -1); + void playMusicAsync(const std::string& filePath, int loops = -1); + void stopMusicAsync(); + void pauseMusicAsync(); + void resumeMusicAsync(); + void setMusicVolume(int volume); // 0..128 + void setSoundVolume(int volume); // 0..128 - void playMusicAsync(std::string musicName); - - void stopAsync(); - - void exit() - { - stop = true; - } - - std::thread worker; + void exit() { stop = true; } private: - std::unique_ptr audioPlayer; - //std::mutex audioPlayerMutex; - - std::mutex soundNameMutex; - std::mutex musicNameMutex; - - std::string latestSoundName; - std::string latestMusicName; + void workerThread(); + std::thread worker; std::mutex mtx; std::condition_variable cv; std::queue> taskQueue; bool stop = false; - void workerThread(); + std::unordered_map soundCache; + std::mutex soundCacheMutex; -}; - -#endif \ No newline at end of file + bool initialized = false; +}; \ No newline at end of file diff --git a/src/Game.cpp b/src/Game.cpp index 6eeacff..2d73298 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -55,6 +55,7 @@ namespace ZL : newTickCount(0) , lastTickCount(0) , menuManager(renderer) + , audioPlayer(std::make_unique()) { } @@ -211,6 +212,15 @@ namespace ZL loadingCompleted = true; + if (audioPlayer->init()) { + audioPlayer->setMusicVolume(100); + audioPlayer->setSoundVolume(80); + std::cout << "Audio initialized successfully" << std::endl; + } + else { + std::cout << "Audio initialization failed" << std::endl; + } + } void Game::drawUI() @@ -474,16 +484,17 @@ namespace ZL } } - - if (event.type == SDL_KEYDOWN) { - if (currentLocation && currentLocation->dialogueSystem.handleKeyDown(event.key.keysym.sym)) - { - continue; - } - } - if (event.type == SDL_KEYDOWN && event.key.repeat == 0) { switch (event.key.keysym.sym) { + case SDLK_1: + if (audioPlayer) audioPlayer->playSoundAsync("resources/sounds/background.wav"); + break; + case SDLK_2: + if (audioPlayer) audioPlayer->playMusicAsync("resources/sounds/lullaby-music-vol20-186394--online-audio-convert.com.ogg"); + break; + case SDLK_3: + if (audioPlayer) audioPlayer->stopMusicAsync(); + break; case SDLK_f: currentLocation->dialogueSystem.startDialogue("test_choice_dialogue"); break; diff --git a/src/Game.h b/src/Game.h index 51920c8..7beab7b 100644 --- a/src/Game.h +++ b/src/Game.h @@ -20,6 +20,7 @@ #include #include #include "Location.h" +#include "AudioPlayerAsync.h" namespace ZL { @@ -56,6 +57,7 @@ namespace ZL { bool rightMouseDown = false; int lastMouseX = 0; int lastMouseY = 0; + std::unique_ptr audioPlayer; int64_t getSyncTimeMs(); void processTickCount();