diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index a020bef060..e31f33f308 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -13,7 +13,7 @@ jobs: - name: configure run: | sudo apt-get update - sudo apt-get install libsdl2-dev libfreeimage-dev libfreetype6-dev libcurl4-openssl-dev rapidjson-dev libasound2-dev libgl1-mesa-dev build-essential cmake fonts-droid-fallback libvlc-dev libvlccore-dev vlc-bin + sudo apt-get install libsdl2-dev libsdl2-mixer-dev libfreeimage-dev libfreetype6-dev libcurl4-openssl-dev rapidjson-dev libasound2-dev libgl1-mesa-dev build-essential cmake fonts-droid-fallback libvlc-dev libvlccore-dev vlc-bin shell: bash - name: make run: | diff --git a/CMake/Packages/FindSDL2_mixer.cmake b/CMake/Packages/FindSDL2_mixer.cmake new file mode 100644 index 0000000000..a71f26a83b --- /dev/null +++ b/CMake/Packages/FindSDL2_mixer.cmake @@ -0,0 +1,220 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +# Copyright 2019 Amine Ben Hassouna +# Copyright 2000-2019 Kitware, Inc. and Contributors +# All rights reserved. + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: + +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. + +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. + +# * Neither the name of Kitware, Inc. nor the names of Contributors +# may be used to endorse or promote products derived from this +# software without specific prior written permission. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#[=======================================================================[.rst: +FindSDL2_mixer +-------------- + +Locate SDL2_mixer library + +This module defines the following 'IMPORTED' target: + +:: + + SDL2::Mixer + The SDL2_mixer library, if found. + Have SDL2::Core as a link dependency. + + + +This module will set the following variables in your project: + +:: + + SDL2_MIXER_LIBRARIES, the name of the library to link against + SDL2_MIXER_INCLUDE_DIRS, where to find the headers + SDL2_MIXER_FOUND, if false, do not try to link against + SDL2_MIXER_VERSION_STRING - human-readable string containing the + version of SDL2_mixer + +This module responds to the following cache variables: + +:: + + SDL2_MIXER_PATH + Set a custom SDL2_mixer Library path (default: empty) + + SDL2_MIXER_NO_DEFAULT_PATH + Disable search SDL2_mixer Library in default path. + If SDL2_MIXER_PATH (default: ON) + Else (default: OFF) + + SDL2_MIXER_INCLUDE_DIR + SDL2_mixer headers path. + + SDL2_MIXER_LIBRARY + SDL2_mixer Library (.dll, .so, .a, etc) path. + + +Additional Note: If you see an empty SDL2_MIXER_LIBRARY in your project +configuration, it means CMake did not find your SDL2_mixer library +(SDL2_mixer.dll, libsdl2_mixer.so, etc). Set SDL2_MIXER_LIBRARY to point +to your SDL2_mixer library, and configure again. This value is used to +generate the final SDL2_MIXER_LIBRARIES variable and the SDL2::Mixer target, +but when this value is unset, SDL2_MIXER_LIBRARIES and SDL2::Mixer does not +get created. + + +$SDL2MIXERDIR is an environment variable that would correspond to the +./configure --prefix=$SDL2MIXERDIR used in building SDL2_mixer. + +$SDL2DIR is an environment variable that would correspond to the +./configure --prefix=$SDL2DIR used in building SDL2. + + + +Created by Amine Ben Hassouna: + Adapt FindSDL_mixer.cmake to SDL2_mixer (FindSDL2_mixer.cmake). + Add cache variables for more flexibility: + SDL2_MIXER_PATH, SDL2_MIXER_NO_DEFAULT_PATH (for details, see doc above). + Add SDL2 as a required dependency. + Modernize the FindSDL2_mixer.cmake module by creating a specific target: + SDL2::Mixer (for details, see doc above). + +Original FindSDL_mixer.cmake module: + Created by Eric Wing. This was influenced by the FindSDL.cmake + module, but with modifications to recognize OS X frameworks and + additional Unix paths (FreeBSD, etc). +#]=======================================================================] + +# SDL2 Library required +find_package(SDL2 QUIET) +if(NOT SDL2_FOUND) + set(SDL2_MIXER_SDL2_NOT_FOUND "Could NOT find SDL2 (SDL2 is required by SDL2_mixer).") + if(SDL2_mixer_FIND_REQUIRED) + message(FATAL_ERROR ${SDL2_MIXER_SDL2_NOT_FOUND}) + else() + if(NOT SDL2_mixer_FIND_QUIETLY) + message(STATUS ${SDL2_MIXER_SDL2_NOT_FOUND}) + endif() + return() + endif() + unset(SDL2_MIXER_SDL2_NOT_FOUND) +endif() + + +# Define options for searching SDL2_mixer Library in a custom path + +set(SDL2_MIXER_PATH "" CACHE STRING "Custom SDL2_mixer Library path") + +set(_SDL2_MIXER_NO_DEFAULT_PATH OFF) +if(SDL2_MIXER_PATH) + set(_SDL2_MIXER_NO_DEFAULT_PATH ON) +endif() + +set(SDL2_MIXER_NO_DEFAULT_PATH ${_SDL2_MIXER_NO_DEFAULT_PATH} + CACHE BOOL "Disable search SDL2_mixer Library in default path") +unset(_SDL2_MIXER_NO_DEFAULT_PATH) + +set(SDL2_MIXER_NO_DEFAULT_PATH_CMD) +if(SDL2_MIXER_NO_DEFAULT_PATH) + set(SDL2_MIXER_NO_DEFAULT_PATH_CMD NO_DEFAULT_PATH) +endif() + +# Search for the SDL2_mixer include directory +find_path(SDL2_MIXER_INCLUDE_DIR SDL_mixer.h + HINTS + ENV SDL2MIXERDIR + ENV SDL2DIR + ${SDL2_MIXER_NO_DEFAULT_PATH_CMD} + PATH_SUFFIXES SDL2 + # path suffixes to search inside ENV{SDL2DIR} + # and ENV{SDL2MIXERDIR} + include/SDL2 include + PATHS ${SDL2_MIXER_PATH} + DOC "Where the SDL2_mixer headers can be found" +) + +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(VC_LIB_PATH_SUFFIX lib/x64) +else() + set(VC_LIB_PATH_SUFFIX lib/x86) +endif() + +# Search for the SDL2_mixer library +find_library(SDL2_MIXER_LIBRARY + NAMES SDL2_mixer + HINTS + ENV SDL2MIXERDIR + ENV SDL2DIR + ${SDL2_MIXER_NO_DEFAULT_PATH_CMD} + PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX} + PATHS ${SDL2_MIXER_PATH} + DOC "Where the SDL2_mixer Library can be found" +) + +# Read SDL2_mixer version +if(SDL2_MIXER_INCLUDE_DIR AND EXISTS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h") + file(STRINGS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h" SDL2_MIXER_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_MIXER_MAJOR_VERSION[ \t]+[0-9]+$") + file(STRINGS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h" SDL2_MIXER_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_MIXER_MINOR_VERSION[ \t]+[0-9]+$") + file(STRINGS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h" SDL2_MIXER_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_MIXER_PATCHLEVEL[ \t]+[0-9]+$") + string(REGEX REPLACE "^#define[ \t]+SDL_MIXER_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_MIXER_VERSION_MAJOR "${SDL2_MIXER_VERSION_MAJOR_LINE}") + string(REGEX REPLACE "^#define[ \t]+SDL_MIXER_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_MIXER_VERSION_MINOR "${SDL2_MIXER_VERSION_MINOR_LINE}") + string(REGEX REPLACE "^#define[ \t]+SDL_MIXER_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_MIXER_VERSION_PATCH "${SDL2_MIXER_VERSION_PATCH_LINE}") + set(SDL2_MIXER_VERSION_STRING ${SDL2_MIXER_VERSION_MAJOR}.${SDL2_MIXER_VERSION_MINOR}.${SDL2_MIXER_VERSION_PATCH}) + unset(SDL2_MIXER_VERSION_MAJOR_LINE) + unset(SDL2_MIXER_VERSION_MINOR_LINE) + unset(SDL2_MIXER_VERSION_PATCH_LINE) + unset(SDL2_MIXER_VERSION_MAJOR) + unset(SDL2_MIXER_VERSION_MINOR) + unset(SDL2_MIXER_VERSION_PATCH) +endif() + +set(SDL2_MIXER_LIBRARIES ${SDL2_MIXER_LIBRARY}) +set(SDL2_MIXER_INCLUDE_DIRS ${SDL2_MIXER_INCLUDE_DIR}) + +include(FindPackageHandleStandardArgs) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2_mixer + REQUIRED_VARS SDL2_MIXER_LIBRARIES SDL2_MIXER_INCLUDE_DIRS + VERSION_VAR SDL2_MIXER_VERSION_STRING) + + +mark_as_advanced(SDL2_MIXER_PATH + SDL2_MIXER_NO_DEFAULT_PATH + SDL2_MIXER_LIBRARY + SDL2_MIXER_INCLUDE_DIR) + + +if(SDL2_MIXER_FOUND) + + # SDL2::Mixer target + if(SDL2_MIXER_LIBRARY AND NOT TARGET SDL2::Mixer) + add_library(SDL2::Mixer UNKNOWN IMPORTED) + set_target_properties(SDL2::Mixer PROPERTIES + IMPORTED_LOCATION "${SDL2_MIXER_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${SDL2_MIXER_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES SDL2::Core) + endif() +endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index eb9f403ccf..2a869ac66c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,6 +90,7 @@ endif() find_package(Freetype REQUIRED) find_package(FreeImage REQUIRED) find_package(SDL2 REQUIRED) +find_package(SDL2_mixer REQUIRED) find_package(CURL REQUIRED) find_package(VLC REQUIRED) find_package(RapidJSON REQUIRED) @@ -160,6 +161,7 @@ set(COMMON_INCLUDE_DIRS ${FREETYPE_INCLUDE_DIRS} ${FreeImage_INCLUDE_DIRS} ${SDL2_INCLUDE_DIR} + ${SDL2_MIXER_INCLUDE_DIR} ${CURL_INCLUDE_DIR} ${VLC_INCLUDE_DIR} ${RAPIDJSON_INCLUDE_DIRS} @@ -198,6 +200,7 @@ set(COMMON_LIBRARIES ${FREETYPE_LIBRARIES} ${FreeImage_LIBRARIES} ${SDL2_LIBRARY} + ${SDL2_MIXER_LIBRARY} ${CURL_LIBRARIES} ${VLC_LIBRARIES} pugixml diff --git a/CREDITS.md b/CREDITS.md index babde810f5..ba8fc97b5d 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,6 +1,11 @@ Programming Alec "Aloshi" Lofquist - http://www.aloshi.com +BGM Player using SDL2-mixer + Nicolas Adenis-Lamarre - https://batocera.org + Fabrice Caruso - https://batocera.org + Andrew Falcon - bluestang2006@gmail.com (backported code) + UI Art & Design Nils Bonenberger diff --git a/es-app/CMakeLists.txt b/es-app/CMakeLists.txt index 4ff4fdcce9..b00dcc013f 100644 --- a/es-app/CMakeLists.txt +++ b/es-app/CMakeLists.txt @@ -158,8 +158,8 @@ SET(CPACK_RESOURCE_FILE README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "Alec Lofquist ") SET(CPACK_DEBIAN_PACKAGE_SECTION "misc") SET(CPACK_DEBIAN_PACKAGE_PRIORITY "extra") -SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6, libsdl2-2.0-0, libfreeimage3, libfreetype6, libcurl3, libasound2") -SET(CPACK_DEBIAN_PACKAGE_BUILDS_DEPENDS "debhelper (>= 8.0.0), cmake, g++ (>= 4.8), libsdl2-dev, libfreeimage-dev, libfreetype6-dev, libcurl4-openssl-dev, libasound2-dev, libgl1-mesa-dev, rapidjson-dev") +SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6, libsdl2-2.0-0, libsdl2-mixer-2.0-0, libfreeimage3, libfreetype6, libcurl3, libasound2") +SET(CPACK_DEBIAN_PACKAGE_BUILDS_DEPENDS "debhelper (>= 8.0.0), cmake, g++ (>= 4.8), libsdl2-dev, libsdl2-mixer-dev, libfreeimage-dev, libfreetype6-dev, libcurl4-openssl-dev, libasound2-dev, libgl1-mesa-dev, rapidjson-dev") SET(CPACK_PACKAGE_VENDOR "emulationstation.org") SET(CPACK_PACKAGE_VERSION "2.0.0~rc1") diff --git a/es-app/src/EmulationStation.h b/es-app/src/EmulationStation.h index 24e83971ca..1e8f3e6587 100644 --- a/es-app/src/EmulationStation.h +++ b/es-app/src/EmulationStation.h @@ -5,13 +5,14 @@ // These numbers and strings need to be manually updated for a new version. // Do this version number update as the very last commit for the new release version. #define PROGRAM_VERSION_MAJOR 2 -#define PROGRAM_VERSION_MINOR 11 -#define PROGRAM_VERSION_MAINTENANCE 0 -#define PROGRAM_VERSION_STRING "2.11.0rp-dev" +#define PROGRAM_VERSION_MINOR 10 +#define PROGRAM_VERSION_MAINTENANCE 2 +#define PROGRAM_VERSION_STRING "2.10.2rp" #define PROGRAM_BUILT_STRING __DATE__ " - " __TIME__ -#define RESOURCE_VERSION_STRING "2,11,0\0" +#define RESOURCE_VERSION_STRING "2,10,2\0" + #define RESOURCE_VERSION PROGRAM_VERSION_MAJOR,PROGRAM_VERSION_MINOR,PROGRAM_VERSION_MAINTENANCE #endif // ES_APP_EMULATION_STATION_H diff --git a/es-app/src/FileData.cpp b/es-app/src/FileData.cpp index 5d8d5fa0e0..f2d6b27136 100644 --- a/es-app/src/FileData.cpp +++ b/es-app/src/FileData.cpp @@ -299,6 +299,7 @@ void FileData::launchGame(Window* window) window->init(); VolumeControl::getInstance()->init(); + AudioManager::getInstance()->init(); window->normalizeNextUpdate(); //update number of times the game has been launched @@ -313,6 +314,8 @@ void FileData::launchGame(Window* window) CollectionSystemManager::get()->refreshCollectionSystems(gameToUpdate); gameToUpdate->mSystem->onMetaDataSavePoint(); + + AudioManager::getInstance()->playRandomMusic(); } CollectionFileData::CollectionFileData(FileData* file, SystemData* system) diff --git a/es-app/src/VolumeControl.cpp b/es-app/src/VolumeControl.cpp index 80d18dfdc0..edf5cba6ad 100644 --- a/es-app/src/VolumeControl.cpp +++ b/es-app/src/VolumeControl.cpp @@ -119,9 +119,39 @@ void VolumeControl::init() } else { - LOG(LogError) << "VolumeControl::init() - Failed to find mixer elements!"; - snd_mixer_close(mixerHandle); - mixerHandle = nullptr; + LOG(LogInfo) << "VolumeControl::init() - Unable to find mixer " << mixerName << " -> Search for alternative mixer"; + + snd_mixer_selem_id_t *mxid = nullptr; + snd_mixer_selem_id_alloca(&mxid); + + for (snd_mixer_elem_t* mxe = snd_mixer_first_elem(mixerHandle); mxe != nullptr; mxe = snd_mixer_elem_next(mxe)) + { + if (snd_mixer_selem_has_playback_volume(mxe) != 0 && snd_mixer_selem_is_active(mxe) != 0) + { + snd_mixer_selem_get_id(mxe, mxid); + mixerName = snd_mixer_selem_id_get_name(mxid); + + LOG(LogInfo) << "mixername : " << mixerName; + + snd_mixer_selem_id_set_name(mixerSelemId, mixerName); + mixerElem = snd_mixer_find_selem(mixerHandle, mixerSelemId); + if (mixerElem != nullptr) + { + LOG(LogDebug) << "VolumeControl::init() - Mixer initialized"; + break; + } + else + { + LOG(LogDebug) << "VolumeControl::init() - Mixer not initialized"; + } + } + } + if (mixerElem == nullptr) + { + LOG(LogError) << "VolumeControl::init() - Failed to find mixer elements!"; + snd_mixer_close(mixerHandle); + mixerHandle = nullptr; + } } } else @@ -260,6 +290,10 @@ int VolumeControl::getVolume() const #elif defined(__linux__) if (mixerElem != nullptr) { + if (mixerHandle != nullptr) + { + snd_mixer_handle_events(mixerHandle); + } //get volume range long minVolume; long maxVolume; @@ -312,6 +346,14 @@ int VolumeControl::getVolume() const { //Windows Vista or above. use EndpointVolume API float floatVolume = 0.0f; //0-1 + BOOL mute = FALSE; + if (endpointVolume->GetMute(&mute) == S_OK) + { + if (mute) + { + return 0; + } + } if (endpointVolume->GetMasterVolumeLevelScalar(&floatVolume) == S_OK) { volume = (int)Math::round(floatVolume * 100.0f); @@ -321,7 +363,6 @@ int VolumeControl::getVolume() const { LOG(LogError) << "VolumeControl::getVolume() - Failed to get master volume!"; } - } #endif //clamp to 0-100 range @@ -404,3 +445,14 @@ void VolumeControl::setVolume(int volume) } #endif } + +bool VolumeControl::isAvailable() +{ +#if defined (__APPLE__) + return false; +#elif defined(__linux__) + return mixerHandle != nullptr && mixerElem != nullptr; +#elif defined(WIN32) || defined(_WIN32) + return mixerHandle != nullptr || endpointVolume != nullptr; +#endif +} diff --git a/es-app/src/VolumeControl.h b/es-app/src/VolumeControl.h index a2e420e7e2..ea2ef8c913 100644 --- a/es-app/src/VolumeControl.h +++ b/es-app/src/VolumeControl.h @@ -51,6 +51,8 @@ class VolumeControl void init(); void deinit(); + bool isAvailable(); + int getVolume() const; void setVolume(int volume); diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index 224e6511b5..6bf6ba0673 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -11,6 +11,7 @@ #include "guis/GuiSettings.h" #include "views/UIModeController.h" #include "views/ViewController.h" +#include "AudioManager.h" #include "CollectionSystemManager.h" #include "EmulationStation.h" #include "Scripting.h" @@ -90,8 +91,16 @@ void GuiMenu::openSoundSettings() // volume auto volume = std::make_shared(mWindow, 0.f, 100.f, 1.f, "%"); volume->setValue((float)VolumeControl::getInstance()->getVolume()); - s->addWithLabel("SYSTEM VOLUME", volume); - s->addSaveFunc([volume] { VolumeControl::getInstance()->setVolume((int)Math::round(volume->getValue())); }); + volume->setOnValueChanged([](const float &newVal) { VolumeControl::getInstance()->setVolume((int)Math::round(newVal)); }); + s->addWithLabel("AUDIO VOLUME", volume); + s->addSaveFunc([this, volume] { VolumeControl::getInstance()->setVolume((int)Math::round(volume->getValue())); }); + + // music volume + auto musicVolume = std::make_shared(mWindow, 0.f, 100.f, 1.f, "%"); + musicVolume->setValue(Settings::getInstance()->getInt("MusicVolume")); + musicVolume->setOnValueChanged([](const float &newVal) { Settings::getInstance()->setInt("MusicVolume", (int)round(newVal)); }); + s->addWithLabel("MUSIC VOLUME", musicVolume); + s->addSaveFunc([this, musicVolume] { Settings::getInstance()->setInt("MusicVolume", (int)round(musicVolume->getValue())); }); if (UIModeController::getInstance()->isUIModeFull()) { @@ -158,6 +167,17 @@ void GuiMenu::openSoundSettings() } Settings::getInstance()->setBool("EnableSounds", sounds_enabled->getState()); }); + + auto music_enabled = std::make_shared(mWindow); + music_enabled->setState(Settings::getInstance()->getBool("EnableMusic")); + s->addWithLabel("BACKGROUND MUSIC", music_enabled); + s->addSaveFunc([music_enabled] { + Settings::getInstance()->setBool("EnableMusic", music_enabled->getState()); + if (music_enabled->getState()) + AudioManager::getInstance()->playRandomMusic(); + else + AudioManager::getInstance()->stopMusic(); + }); auto video_audio = std::make_shared(mWindow); video_audio->setState(Settings::getInstance()->getBool("VideoAudio")); diff --git a/es-app/src/main.cpp b/es-app/src/main.cpp index 956d4aed01..8d64f77cad 100644 --- a/es-app/src/main.cpp +++ b/es-app/src/main.cpp @@ -6,6 +6,7 @@ #include "utils/FileSystemUtil.h" #include "utils/ProfilingUtil.h" #include "views/ViewController.h" +#include "AudioManager.h" #include "CollectionSystemManager.h" #include "EmulationStation.h" #include "InputManager.h" @@ -394,6 +395,10 @@ int main(int argc, char* argv[]) for(Uint32 ev_type: event_list) { SDL_FlushEvent(ev_type); } + + // RetroPie BGM + AudioManager::getInstance()->init(); + AudioManager::getInstance()->playRandomMusic(); int lastTime = SDL_GetTicks(); int ps_time = SDL_GetTicks(); diff --git a/es-core/src/AudioManager.cpp b/es-core/src/AudioManager.cpp index 1d0f8ac495..90b8da17ef 100644 --- a/es-core/src/AudioManager.cpp +++ b/es-core/src/AudioManager.cpp @@ -4,54 +4,14 @@ #include "Settings.h" #include "Sound.h" #include +#include "utils/FileSystemUtil.h" +#include "utils/StringUtil.h" +#include +AudioManager* AudioManager::sInstance = NULL; std::vector> AudioManager::sSoundVector; -SDL_AudioSpec AudioManager::sAudioFormat; -std::shared_ptr AudioManager::sInstance; - -void AudioManager::mixAudio(void* /*unused*/, Uint8 *stream, int len) -{ - bool stillPlaying = false; - - //initialize the buffer to "silence" - SDL_memset(stream, 0, len); - - //iterate through all our samples - std::vector>::const_iterator soundIt = sSoundVector.cbegin(); - while (soundIt != sSoundVector.cend()) - { - std::shared_ptr sound = *soundIt; - if(sound->isPlaying()) - { - //calculate rest length of current sample - Uint32 restLength = (sound->getLength() - sound->getPosition()); - if (restLength > (Uint32)len) { - //if stream length is smaller than smaple lenght, clip it - restLength = len; - } - //mix sample into stream - SDL_MixAudio(stream, &(sound->getData()[sound->getPosition()]), restLength, SDL_MIX_MAXVOLUME); - if (sound->getPosition() + restLength < sound->getLength()) - { - //sample hasn't ended yet - stillPlaying = true; - } - //set new sound position. if this is at or beyond the end of the sample, it will stop automatically - sound->setPosition(sound->getPosition() + restLength); - } - //advance to next sound - ++soundIt; - } - - //we have processed all samples. check if some will still be playing - if (!stillPlaying) { - //no. pause audio till a Sound::play() wakes us up - SDL_PauseAudio(1); - } -} - -AudioManager::AudioManager() +AudioManager::AudioManager() : mInitialized(false), mCurrentMusic(nullptr), mMusicVolume(Settings::getInstance()->getInt("MusicVolume")) { init(); } @@ -61,54 +21,69 @@ AudioManager::~AudioManager() deinit(); } -std::shared_ptr & AudioManager::getInstance() +AudioManager* AudioManager::getInstance() { //check if an AudioManager instance is already created, if not create one - if (sInstance == nullptr && Settings::getInstance()->getBool("EnableSounds")) { - sInstance = std::shared_ptr(new AudioManager); - } + if(sInstance == nullptr) + sInstance = new AudioManager(); + return sInstance; } +bool AudioManager::isInitialized() +{ + if(sInstance == nullptr) + return false; + + return sInstance->mInitialized; +} + void AudioManager::init() { - if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) + if(mInitialized) + return; + + if(SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) { LOG(LogError) << "Error initializing SDL audio!\n" << SDL_GetError(); return; } - //stop playing all Sounds - for(unsigned int i = 0; i < sSoundVector.size(); i++) + // Open the audio device and pause + if(Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 4096) < 0) + LOG(LogError) << "MUSIC Error - Unable to open SDLMixer audio: " << SDL_GetError() << std::endl; + else { - if(sSoundVector.at(i)->isPlaying()) - { - sSoundVector[i]->stop(); - } - } + LOG(LogInfo) << "SDL AUDIO Initialized"; + mInitialized = true; - //Set up format and callback. Play 16-bit stereo audio at 44.1Khz - sAudioFormat.freq = 44100; - sAudioFormat.format = AUDIO_S16; - sAudioFormat.channels = 2; - sAudioFormat.samples = 4096; - sAudioFormat.callback = mixAudio; - sAudioFormat.userdata = NULL; - - //Open the audio device and pause - if (SDL_OpenAudio(&sAudioFormat, NULL) < 0) { - LOG(LogError) << "AudioManager Error - Unable to open SDL audio: " << SDL_GetError() << std::endl; + // Reload known sounds + for(unsigned int i = 0; i < sSoundVector.size(); i++) + sSoundVector[i]->init(); } } void AudioManager::deinit() { + if(!mInitialized) + return; + + mInitialized = false; + //stop all playback stop(); + stopMusic(); + + // Free known sounds from memory + for(unsigned int i = 0; i < sSoundVector.size(); i++) + sSoundVector[i]->deinit(); + + Mix_HookMusicFinished(nullptr); + Mix_HaltMusic(); + //completely tear down SDL audio. else SDL hogs audio resources and emulators might fail to start... - SDL_CloseAudio(); + Mix_CloseAudio(); SDL_QuitSubSystem(SDL_INIT_AUDIO); - sInstance = NULL; } void AudioManager::registerSound(std::shared_ptr & sound) @@ -135,21 +110,175 @@ void AudioManager::unregisterSound(std::shared_ptr & sound) void AudioManager::play() { getInstance(); - - //unpause audio, the mixer will figure out if samples need to be played... - SDL_PauseAudio(0); } void AudioManager::stop() { //stop playing all Sounds for(unsigned int i = 0; i < sSoundVector.size(); i++) - { if(sSoundVector.at(i)->isPlaying()) - { sSoundVector[i]->stop(); +} + +void AudioManager::getMusicIn(const std::string &path, std::vector& all_matching_files) +{ + if(!Utils::FileSystem::isDirectory(path)) { + return; + } + auto dirContent = Utils::FileSystem::getDirContent(path); + for(auto it = dirContent.cbegin(); it != dirContent.cend(); ++it) + { + if(Utils::FileSystem::isDirectory(*it)) + { + if(*it == "." || *it == "..") + getMusicIn(*it, all_matching_files); + + } + else + { + std::string extension = Utils::String::toLower(Utils::FileSystem::getExtension(*it)); + if(extension == ".mp3" || extension == ".ogg") + all_matching_files.push_back(*it); } + } +} + +void AudioManager::playRandomMusic(bool continueIfPlaying) +{ + if(!Settings::getInstance()->getBool("EnableMusic")) + return; + + std::vector musics; + + // check in RetroPie music directory + if(musics.empty()) + getMusicIn(Utils::FileSystem::getHomePath() + "/RetroPie/roms/music", musics); + + // check in system sound directory + if(musics.empty()) + getMusicIn("/opt/retropie/music", musics); + + // check in .emulationstation/music directory + if(musics.empty()) + getMusicIn(Utils::FileSystem::getHomePath() + "/.emulationstation/music", musics); + + if(musics.empty()) + return; + +#if defined(WIN32) + srand(time(NULL) % getpid()); +#else + srand(time(NULL) % getpid() + getppid()); +#endif + + int randomIndex = rand() % musics.size(); + + // continue playing ? + if(mCurrentMusic != nullptr && continueIfPlaying) + return; + + playMusic(musics.at(randomIndex)); +} + +void AudioManager::playMusic(std::string path) +{ + if(!mInitialized) + return; + + // free the previous music + stopMusic(false); + + if(!Settings::getInstance()->getBool("EnableMusic")) + return; + + // load a new music + mCurrentMusic = Mix_LoadMUS(path.c_str()); + if(mCurrentMusic == NULL) + { + LOG(LogError) << Mix_GetError() << " for " << path; + return; + } + + if(Mix_FadeInMusic(mCurrentMusic, 1, 1000) == -1) + { + stopMusic(); + return; + } + + Mix_HookMusicFinished(AudioManager::musicEnd_callback); +} + +void AudioManager::musicEnd_callback() +{ + if(!Settings::getInstance()->getBool("EnableMusic")) + return; + else + AudioManager::getInstance()->playRandomMusic(false); +} + +void AudioManager::stopMusic(bool fadeOut) +{ + if(mCurrentMusic == NULL) + return; + + Mix_HookMusicFinished(nullptr); + + if(fadeOut) + { + while(!Mix_FadeOutMusic(2000) && Mix_PlayingMusic()) + SDL_Delay(100); + } + + Mix_FreeMusic(mCurrentMusic); + mCurrentMusic = NULL; +} + +int AudioManager::getMaxMusicVolume() +{ + int ret = (Settings::getInstance()->getInt("MusicVolume") * MIX_MAX_VOLUME) / 100; + if(ret > MIX_MAX_VOLUME) + return MIX_MAX_VOLUME; + + if(ret < 0) + return 0; + + return ret; +} + +void AudioManager::update(int deltaTime) +{ + if(sInstance == nullptr || !sInstance->mInitialized || !Settings::getInstance()->getBool("EnableMusic")) + return; + + Mix_VolumeMusic(Settings::getInstance()->getInt("MusicVolume")); + + /* TODO: This is part of unimplemented feature that would + * lower the music volume if a video snap was playing + + float deltaVol = deltaTime / 8.0f; + + // #define MINVOL 5 + + int maxVol = getMaxMusicVolume(); + int minVol = maxVol / 20; + if(maxVol > 0 && minVol == 0) + minVol = 1; + + if (sInstance->mMusicVolume > minVol) + { + sInstance->mMusicVolume -= deltaVol; + if (sInstance->mMusicVolume < minVol) + sInstance->mMusicVolume = minVol; + + Mix_VolumeMusic((int)sInstance->mMusicVolume); + } + else if (sInstance->mMusicVolume < maxVol) + { + sInstance->mMusicVolume += deltaVol; + if (sInstance->mMusicVolume > maxVol) + sInstance->mMusicVolume = maxVol; + + Mix_VolumeMusic((int)sInstance->mMusicVolume); } - //pause audio - SDL_PauseAudio(1); + */ } diff --git a/es-core/src/AudioManager.h b/es-core/src/AudioManager.h index e27ce1dc2e..797a81fcdd 100644 --- a/es-core/src/AudioManager.h +++ b/es-core/src/AudioManager.h @@ -5,21 +5,30 @@ #include #include #include +#include "SDL_mixer.h" +#include +#include class Sound; class AudioManager { - static SDL_AudioSpec sAudioFormat; +private: + AudioManager(); + static std::vector> sSoundVector; - static std::shared_ptr sInstance; + static AudioManager* sInstance; - static void mixAudio(void *unused, Uint8 *stream, int len); + Mix_Music* mCurrentMusic; + void getMusicIn(const std::string &path, std::vector& all_matching_files); + void playMusic(std::string path); + static void musicEnd_callback(); - AudioManager(); + bool mInitialized; public: - static std::shared_ptr & getInstance(); + static AudioManager* getInstance(); + static bool isInitialized(); void init(); void deinit(); @@ -30,7 +39,15 @@ class AudioManager void play(); void stop(); + // RetroPie Mixer + void playRandomMusic(bool continueIfPlaying = true); + void stopMusic(bool fadeOut = true); + virtual ~AudioManager(); + + float mMusicVolume; + static void update(int deltaTime); + static int getMaxMusicVolume(); }; #endif // ES_CORE_AUDIO_MANAGER_H diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index 2eab663bc4..949461a73f 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -72,6 +72,8 @@ void Settings::setDefaults() mBoolMap["VSync"] = true; mBoolMap["EnableSounds"] = true; + mBoolMap["EnableMusic"] = true; + mIntMap["MusicVolume"] = 128; mBoolMap["ShowHelpPrompts"] = true; mBoolMap["DoublePressRemovesFromFavs"] = false; mBoolMap["ScrapeRatings"] = true; @@ -286,7 +288,7 @@ void Settings::processBackwardCompatibility() } \ void Settings::setMethodName(const std::string& name, type value) \ { \ - mapName[name] = value; \ + mapName[name] = value; \ } SETTINGS_GETSET(bool, mBoolMap, getBool, setBool); diff --git a/es-core/src/Settings.h b/es-core/src/Settings.h index 51d19a798c..69b18f3e17 100644 --- a/es-core/src/Settings.h +++ b/es-core/src/Settings.h @@ -26,6 +26,12 @@ class Settings void setFloat(const std::string& name, float value); void setString(const std::string& name, const std::string& value); + std::map& getStringMap() { return mStringMap; } + + static bool DebugText; + static bool DebugImage; + static bool DebugGrid; + private: static Settings* sInstance; @@ -40,6 +46,13 @@ class Settings std::map mIntMap; std::map mFloatMap; std::map mStringMap; + + bool mWasChanged; + + std::map mDefaultBoolMap; + std::map mDefaultIntMap; + std::map mDefaultFloatMap; + std::map mDefaultStringMap; }; #endif // ES_CORE_SETTINGS_H diff --git a/es-core/src/Sound.cpp b/es-core/src/Sound.cpp index e3f46e279b..72be6b29f6 100644 --- a/es-core/src/Sound.cpp +++ b/es-core/src/Sound.cpp @@ -14,8 +14,13 @@ std::shared_ptr Sound::get(const std::string& path) return it->second; std::shared_ptr sound = std::shared_ptr(new Sound(path)); - AudioManager::getInstance()->registerSound(sound); - sMap[path] = sound; + + if(AudioManager::isInitialized()) + { + AudioManager::getInstance()->registerSound(sound); + sMap[path] = sound; + } + return sound; } @@ -33,7 +38,7 @@ std::shared_ptr Sound::getFromTheme(const std::shared_ptr& the return get(elem->get("path")); } -Sound::Sound(const std::string & path) : mSampleData(NULL), mSamplePos(0), mSampleLength(0), playing(false) +Sound::Sound(const std::string & path) : mSampleData(NULL), mPlayingChannel(-1) { loadFile(path); } @@ -51,131 +56,57 @@ void Sound::loadFile(const std::string & path) void Sound::init() { - if(mSampleData != NULL) - deinit(); + deinit(); + + if(!AudioManager::isInitialized()) + return; - if(mPath.empty()) + if(mPath.empty() || !Utils::FileSystem::exists(mPath)) + return; + + if(!Settings::getInstance()->getBool("EnableSounds")) return; //load wav file via SDL - SDL_AudioSpec wave; - Uint8 * data = NULL; - Uint32 dlen = 0; - if (SDL_LoadWAV(mPath.c_str(), &wave, &data, &dlen) == NULL) { + mSampleData = Mix_LoadWAV(mPath.c_str()); + if(mSampleData == nullptr) + { LOG(LogError) << "Error loading sound \"" << mPath << "\"!\n" << " " << SDL_GetError(); return; } - //build conversion buffer - SDL_AudioCVT cvt; - SDL_BuildAudioCVT(&cvt, wave.format, wave.channels, wave.freq, AUDIO_S16, 2, 44100); - //copy data to conversion buffer - cvt.len = dlen; - cvt.buf = new Uint8[cvt.len * cvt.len_mult]; - memcpy(cvt.buf, data, dlen); - //convert buffer to stereo, 16bit, 44.1kHz - if (SDL_ConvertAudio(&cvt) < 0) { - LOG(LogError) << "Error converting sound \"" << mPath << "\" to 44.1kHz, 16bit, stereo format!\n" << " " << SDL_GetError(); - delete[] cvt.buf; - } - else { - //worked. set up member data - SDL_LockAudio(); - mSampleData = cvt.buf; - mSampleLength = cvt.len_cvt; - mSamplePos = 0; - mSampleFormat.channels = 2; - mSampleFormat.freq = 44100; - mSampleFormat.format = AUDIO_S16; - SDL_UnlockAudio(); - } - //free wav data now - SDL_FreeWAV(data); } void Sound::deinit() { - playing = false; - - if(mSampleData != NULL) - { - SDL_LockAudio(); - delete[] mSampleData; - mSampleData = NULL; - mSampleLength = 0; - mSamplePos = 0; - SDL_UnlockAudio(); - } + if(mSampleData == nullptr) + return; + + stop(); + Mix_FreeChunk(mSampleData); + mSampleData = nullptr; } void Sound::play() { - if(mSampleData == NULL) + if(mSampleData == nullptr) return; - + if(!Settings::getInstance()->getBool("EnableSounds")) return; - AudioManager::getInstance(); - - SDL_LockAudio(); - if (playing) - { - //replay from start. rewind the sample to the beginning - mSamplePos = 0; - - } - else - { - //flag our sample as playing - playing = true; - } - SDL_UnlockAudio(); - //tell the AudioManager to start playing samples - AudioManager::getInstance()->play(); + mPlayingChannel = Mix_PlayChannel(-1, mSampleData, 0); } bool Sound::isPlaying() const { - return playing; + return (mPlayingChannel >= 0); } void Sound::stop() { - //flag our sample as playing and rewind its position - SDL_LockAudio(); - playing = false; - mSamplePos = 0; - SDL_UnlockAudio(); -} - -const Uint8 * Sound::getData() const -{ - return mSampleData; -} - -Uint32 Sound::getPosition() const -{ - return mSamplePos; -} - -void Sound::setPosition(Uint32 newPosition) -{ - mSamplePos = newPosition; - if (mSamplePos >= mSampleLength) { - //got to or beyond the end of the sample. stop playing - playing = false; - mSamplePos = 0; - } -} - -Uint32 Sound::getLength() const -{ - return mSampleLength; -} + if(mPlayingChannel < 0) + return; -Uint32 Sound::getLengthMS() const -{ - //44100 samples per second, 2 channels (stereo) - //I have no idea why the *0.75 is necessary, but otherwise it's inaccurate - return (Uint32)((mSampleLength / 44100.0f / 2.0f * 0.75f) * 1000); + //Mix_HaltChannel(mPlayingChannel); + mPlayingChannel = -1; } diff --git a/es-core/src/Sound.h b/es-core/src/Sound.h index 43e68453cc..4de9961702 100644 --- a/es-core/src/Sound.h +++ b/es-core/src/Sound.h @@ -2,7 +2,7 @@ #ifndef ES_CORE_SOUND_H #define ES_CORE_SOUND_H -#include "SDL_audio.h" +#include "SDL_mixer.h" #include #include #include @@ -12,11 +12,8 @@ class ThemeData; class Sound { std::string mPath; - SDL_AudioSpec mSampleFormat; - Uint8 * mSampleData; - Uint32 mSamplePos; - Uint32 mSampleLength; - bool playing; + Mix_Chunk* mSampleData; + int mPlayingChannel; public: static std::shared_ptr get(const std::string& path); @@ -33,12 +30,6 @@ class Sound bool isPlaying() const; void stop(); - const Uint8 * getData() const; - Uint32 getPosition() const; - void setPosition(Uint32 newPosition); - Uint32 getLength() const; - Uint32 getLengthMS() const; - private: Sound(const std::string & path = ""); static std::map< std::string, std::shared_ptr > sMap; diff --git a/es-core/src/Window.cpp b/es-core/src/Window.cpp index 19acb32f4e..500b0cd3e0 100644 --- a/es-core/src/Window.cpp +++ b/es-core/src/Window.cpp @@ -4,6 +4,7 @@ #include "components/ImageComponent.h" #include "resources/Font.h" #include "resources/TextureResource.h" +#include "AudioManager.h" #include "InputManager.h" #include "Log.h" #include "Scripting.h" @@ -236,6 +237,8 @@ void Window::update(int deltaTime) // Update the screensaver if (mScreenSaver) mScreenSaver->update(deltaTime); + + AudioManager::update(deltaTime); } void Window::render() diff --git a/es-core/src/components/SliderComponent.cpp b/es-core/src/components/SliderComponent.cpp index aba8174c45..dc7a7004d4 100644 --- a/es-core/src/components/SliderComponent.cpp +++ b/es-core/src/components/SliderComponent.cpp @@ -28,7 +28,7 @@ bool SliderComponent::input(InputConfig* config, Input input) mMoveRate = input.value ? -mSingleIncrement : 0; mMoveAccumulator = -MOVE_REPEAT_DELAY; - return true; + return input.value; } if(config->isMappedLike("right", input)) { @@ -37,7 +37,7 @@ bool SliderComponent::input(InputConfig* config, Input input) mMoveRate = input.value ? mSingleIncrement : 0; mMoveAccumulator = -MOVE_REPEAT_DELAY; - return true; + return input.value; } return GuiComponent::input(config, input); @@ -81,6 +81,9 @@ void SliderComponent::render(const Transform4x4f& parentTrans) void SliderComponent::setValue(float value) { + if (mValue == value) + return; + mValue = value; if(mValue < mMin) mValue = mMin; @@ -88,6 +91,9 @@ void SliderComponent::setValue(float value) mValue = mMax; onValueChanged(); + + if (mValueChanged) + mValueChanged(mValue); } float SliderComponent::getValue() diff --git a/es-core/src/components/SliderComponent.h b/es-core/src/components/SliderComponent.h index f3ae181fce..cf0f108856 100644 --- a/es-core/src/components/SliderComponent.h +++ b/es-core/src/components/SliderComponent.h @@ -25,6 +25,8 @@ class SliderComponent : public GuiComponent void onSizeChanged() override; virtual std::vector getHelpPrompts() override; + + inline void setOnValueChanged(const std::function& callback) { mValueChanged = callback; } private: void onValueChanged(); @@ -40,6 +42,8 @@ class SliderComponent : public GuiComponent std::string mSuffix; std::shared_ptr mFont; std::shared_ptr mValueCache; + + std::function mValueChanged; }; #endif // ES_CORE_COMPONENTS_SLIDER_COMPONENT_H