diff --git a/.circleci/config.yml b/.circleci/config.yml index 6ebf1a86a..7ec43bc3b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ jobs: steps: - checkout - run: apt-get update -y - - run: apt-get install -y cmake file g++ git libfmt-dev libsdl2-dev libsdl2-mixer-dev libsdl2-ttf-dev libsodium-dev rpm wget + - run: apt-get install -y cmake file g++ git libfmt-dev libsdl2-dev libsdl2-ttf-dev libsodium-dev rpm wget - run: cmake -S. -Bbuild .. -DNIGHTLY_BUILD=ON -DCMAKE_INSTALL_PREFIX=/usr - run: cmake --build build -j 2 --target package - store_artifacts: {path: ./build/devilutionx, destination: devilutionx_linux_x86_64} @@ -22,7 +22,7 @@ jobs: steps: - checkout - run: apt-get update -y - - run: apt-get install -y cmake curl g++ git lcov libgtest-dev libfmt-dev libsdl2-dev libsdl2-mixer-dev libsdl2-ttf-dev libsodium-dev + - run: apt-get install -y cmake curl g++ git lcov libgtest-dev libfmt-dev libsdl2-dev libsdl2-ttf-dev libsodium-dev - run: cmake -S. -Bbuild -DRUN_TESTS=ON - run: cmake --build build -j 2 - run: cmake --build build -j 2 --target test @@ -36,7 +36,7 @@ jobs: steps: - checkout - run: apt-get update -y - - run: apt-get install -y cmake file g++ git libfmt-dev libsdl-dev libsdl-mixer1.2-dev libsdl-ttf2.0-dev libsodium-dev rpm + - run: apt-get install -y cmake file g++ git libfmt-dev libsdl-dev libsdl-ttf2.0-dev libsodium-dev rpm - run: cmake -S. -Bbuild .. -DNIGHTLY_BUILD=ON -DUSE_SDL1=ON - run: cmake --build build -j 2 --target package - store_artifacts: {path: ./build/devilutionx, destination: devilutionx_linux_x86_64_sdl1} @@ -50,7 +50,7 @@ jobs: - checkout - run: dpkg --add-architecture i386 - run: apt-get update -y - - run: apt-get install -y cmake file g++-multilib git libfmt-dev:i386 libsdl2-dev:i386 libsdl2-mixer-dev:i386 libsdl2-ttf-dev:i386 libsodium-dev:i386 rpm wget + - run: apt-get install -y cmake file g++-multilib git libfmt-dev:i386 libsdl2-dev:i386 libsdl2-ttf-dev:i386 libsodium-dev:i386 rpm wget - run: cmake -S. -Bbuild -DNIGHTLY_BUILD=ON -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_TOOLCHAIN_FILE=../CMake/32bit.cmake - run: cmake --build build -j 2 --target package - store_artifacts: {path: ./build/devilutionx, destination: devilutionx_linux_x86} diff --git a/.editorconfig b/.editorconfig index c3e3f73c5..8239e4ce0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -43,3 +43,9 @@ end_of_line = lf indent_style = space indent_size = 2 end_of_line = crlf + +[control] +end_of_line = lf + +[devilutionx.spec] +end_of_line = lf diff --git a/3rdParty/SDL_audiolib/CMakeLists.txt b/3rdParty/SDL_audiolib/CMakeLists.txt new file mode 100644 index 000000000..2fbcaf8b4 --- /dev/null +++ b/3rdParty/SDL_audiolib/CMakeLists.txt @@ -0,0 +1,35 @@ +include(FetchContent_MakeAvailableExcludeFromAll) + +if(DEVILUTIONX_STATIC_SDL_AUDIOLIB) + set(BUILD_SHARED_LIBS OFF) +else() + set(BUILD_SHARED_LIBS ON) +endif() + +# No need for the libsamplerate resampler: +set(USE_RESAMP_SRC OFF) + +# No need for the SOX resampler: +set(USE_RESAMP_SOXR OFF) + +# We do not need any of the audio formats except WAV: +set(USE_DEC_DRWAV ON) +set(USE_DEC_DRFLAC OFF) +set(USE_DEC_OPENMPT OFF) +set(USE_DEC_XMP OFF) +set(USE_DEC_MODPLUG OFF) +set(USE_DEC_MPG123 OFF) +set(USE_DEC_SNDFILE OFF) +set(USE_DEC_LIBVORBIS OFF) +set(USE_DEC_LIBOPUSFILE OFF) +set(USE_DEC_MUSEPACK OFF) +set(USE_DEC_FLUIDSYNTH OFF) +set(USE_DEC_BASSMIDI OFF) +set(USE_DEC_WILDMIDI OFF) +set(USE_DEC_ADLMIDI OFF) + +include(FetchContent) +FetchContent_Declare(SDL_audiolib + URL https://github.com/realnc/SDL_audiolib/archive/f7c605cb9578916355a5a6770db76a1c0ca84a30.zip + URL_HASH MD5=9ed7ecac15988247650480656bf2929a) +FetchContent_MakeAvailableExcludeFromAll(SDL_audiolib) diff --git a/Brewfile b/Brewfile index 875ae426d..4bcc74f3a 100644 --- a/Brewfile +++ b/Brewfile @@ -1,6 +1,5 @@ brew "cmake" brew "fmt" -brew "sdl2_mixer" brew "sdl2_ttf" brew "libsodium" brew "pkg-config" diff --git a/CMake/FindSDL2_mixer.cmake b/CMake/FindSDL2_mixer.cmake deleted file mode 100644 index d8c4599be..000000000 --- a/CMake/FindSDL2_mixer.cmake +++ /dev/null @@ -1,197 +0,0 @@ -# - Find SDL2_mixer -# Find the SDL2 headers and libraries -# -# SDL2::SDL2_mixer - Imported target -# -# SDL2_mixer_FOUND - True if SDL2_mixer was found. -# SDL2_mixer_DYNAMIC - If we found a DLL version of SDL2_mixer -# -# Modified for SDL2_mixer of FindSDL2.cmake -# Original Author: -# 2015 Ryan Pavlik -# -# Copyright Sensics, Inc. 2015. -# Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE_1_0.txt or copy at -# http://www.boost.org/LICENSE_1_0.txt) - -# Set up architectures (for windows) and prefixes (for mingw builds) -if(WIN32) - if(MINGW) - include(MinGWSearchPathExtras OPTIONAL) - if(MINGWSEARCH_TARGET_TRIPLE) - set(SDL2_mixer_PREFIX ${MINGWSEARCH_TARGET_TRIPLE}) - endif() - endif() - if(CMAKE_SIZEOF_VOID_P EQUAL 8) - set(SDL2_mixer_LIB_PATH_SUFFIX lib/x64) - if(NOT MSVC AND NOT SDL2_mixer_PREFIX) - set(SDL2_mixer_PREFIX x86_64-w64-mingw32) - endif() - else() - set(SDL2_mixer_LIB_PATH_SUFFIX lib/x86) - if(NOT MSVC AND NOT SDL2_mixer_PREFIX) - set(SDL2_mixer_PREFIX i686-w64-mingw32) - endif() - endif() -endif() - -if(SDL2_mixer_PREFIX) - set(SDL2_mixer_ORIGPREFIXPATH ${CMAKE_PREFIX_PATH}) - if(SDL2_mixer_ROOT_DIR) - list(APPEND CMAKE_PREFIX_PATH "${SDL2_mixer_ROOT_DIR}") - endif() - if(CMAKE_PREFIX_PATH) - foreach(_prefix ${CMAKE_PREFIX_PATH}) - list(APPEND CMAKE_PREFIX_PATH "${_prefix}/${SDL2_mixer_PREFIX}") - endforeach() - endif() - if(MINGWSEARCH_PREFIXES) - list(APPEND CMAKE_PREFIX_PATH ${MINGWSEARCH_PREFIXES}) - endif() -endif() - -# Invoke pkgconfig for hints -find_package(PkgConfig QUIET) -set(SDL2_mixer_INCLUDE_HINTS) -set(SDL2_mixer_LIB_HINTS) -if(PKG_CONFIG_FOUND) - pkg_search_module(SDL2_mixerPC QUIET SDL2_mixer) - if(SDL2_mixerPC_INCLUDE_DIRS) - set(SDL2_mixer_INCLUDE_HINTS ${SDL2_mixerPC_INCLUDE_DIRS}) - endif() - if(SDL2_mixerPC_LIBRARY_DIRS) - set(SDL2_mixer_LIB_HINTS ${SDL2_mixerPC_LIBRARY_DIRS}) - endif() -endif() - -include(FindPackageHandleStandardArgs) - -find_library(SDL2_mixer_LIBRARY - NAMES - SDL2_mixer - HINTS - ${SDL2_mixer_LIB_HINTS} - PATHS - ${SDL2_mixer_ROOT_DIR} - ENV SDL2DIR - PATH_SUFFIXES lib SDL2 ${SDL2_mixer_LIB_PATH_SUFFIX}) - -set(_sdl2_framework FALSE) -# Some special-casing if we've found/been given a framework. -# Handles whether we're given the library inside the framework or the framework itself. -if(APPLE AND "${SDL2_mixer_LIBRARY}" MATCHES "(/[^/]+)*.framework(/.*)?$") - set(_sdl2_framework TRUE) - set(SDL2_mixer_FRAMEWORK "${SDL2_mixer_LIBRARY}") - # Move up in the directory tree as required to get the framework directory. - while("${SDL2_mixer_FRAMEWORK}" MATCHES "(/[^/]+)*.framework(/.*)$" AND NOT "${SDL2_mixer_FRAMEWORK}" MATCHES "(/[^/]+)*.framework$") - get_filename_component(SDL2_mixer_FRAMEWORK "${SDL2_mixer_FRAMEWORK}" DIRECTORY) - endwhile() - if("${SDL2_mixer_FRAMEWORK}" MATCHES "(/[^/]+)*.framework$") - set(SDL2_mixer_FRAMEWORK_NAME ${CMAKE_MATCH_1}) - # If we found a framework, do a search for the header ahead of time that will be more likely to get the framework header. - find_path(SDL2_mixer_INCLUDE_DIR - NAMES - SDL_mixer.h - HINTS - "${SDL2_mixer_FRAMEWORK}/Headers/") - else() - # For some reason we couldn't get the framework directory itself. - # Shouldn't happen, but might if something is weird. - unset(SDL2_mixer_FRAMEWORK) - endif() -endif() - -find_path(SDL2_mixer_INCLUDE_DIR - NAMES - SDL_mixer.h - HINTS - ${SDL2_mixer_INCLUDE_HINTS} - PATHS - ${SDL2_mixer_ROOT_DIR} - ENV SDL2DIR - PATH_SUFFIXES include include/sdl2 include/SDL2 SDL2) - -if(WIN32 AND SDL2_mixer_LIBRARY) - find_file(SDL2_mixer_RUNTIME_LIBRARY - NAMES - SDL2_mixer.dll - libSDL2_mixer.dll - HINTS - ${SDL2_mixer_LIB_HINTS} - PATHS - ${SDL2_mixer_ROOT_DIR} - ENV SDL2DIR - PATH_SUFFIXES bin lib ${SDL2_mixer_LIB_PATH_SUFFIX}) -endif() - -if(MINGW AND NOT SDL2_mixerPC_FOUND) - find_library(SDL2_mixer_MINGW_LIBRARY mingw32) - find_library(SDL2_mixer_MWINDOWS_LIBRARY mwindows) -endif() - -if(SDL2_mixer_PREFIX) - # Restore things the way they used to be. - set(CMAKE_PREFIX_PATH ${SDL2_mixer_ORIGPREFIXPATH}) -endif() - -# handle the QUIETLY and REQUIRED arguments and set QUATLIB_FOUND to TRUE if -# all listed variables are TRUE -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(SDL2_mixer - DEFAULT_MSG - SDL2_mixer_LIBRARY - SDL2_mixer_INCLUDE_DIR - ${SDL2_mixer_EXTRA_REQUIRED}) - -if(SDL2_mixer_FOUND) - if(NOT TARGET SDL2::SDL2_mixer) - # Create SDL2::SDL2_mixer - if(WIN32 AND SDL2_mixer_RUNTIME_LIBRARY) - set(SDL2_mixer_DYNAMIC TRUE) - add_library(SDL2::SDL2_mixer SHARED IMPORTED) - set_target_properties(SDL2::SDL2_mixer - PROPERTIES - IMPORTED_IMPLIB "${SDL2_mixer_LIBRARY}" - IMPORTED_LOCATION "${SDL2_mixer_RUNTIME_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${SDL2_mixer_INCLUDE_DIR}" - ) - else() - add_library(SDL2::SDL2_mixer UNKNOWN IMPORTED) - if(SDL2_mixer_FRAMEWORK AND SDL2_mixer_FRAMEWORK_NAME) - # Handle the case that SDL2_mixer is a framework and we were able to decompose it above. - set_target_properties(SDL2::SDL2_mixer PROPERTIES - IMPORTED_LOCATION "${SDL2_mixer_FRAMEWORK}/${SDL2_mixer_FRAMEWORK_NAME}") - elseif(_sdl2_framework AND SDL2_mixer_LIBRARY MATCHES "(/[^/]+)*.framework$") - # Handle the case that SDL2_mixer is a framework and SDL_LIBRARY is just the framework itself. - - # This takes the basename of the framework, without the extension, - # and sets it (as a child of the framework) as the imported location for the target. - # This is the library symlink inside of the framework. - set_target_properties(SDL2::SDL2_mixer PROPERTIES - IMPORTED_LOCATION "${SDL2_mixer_LIBRARY}/${CMAKE_MATCH_1}") - else() - # Handle non-frameworks (including non-Mac), as well as the case that we're given the library inside of the framework - set_target_properties(SDL2::SDL2_mixer PROPERTIES - IMPORTED_LOCATION "${SDL2_mixer_LIBRARY}") - endif() - set_target_properties(SDL2::SDL2_mixer - PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${SDL2_mixer_INCLUDE_DIR}" - ) - endif() - endif() - mark_as_advanced(SDL2_mixer_ROOT_DIR) -endif() - -mark_as_advanced(SDL2_mixer_LIBRARY - SDL2_mixer_RUNTIME_LIBRARY - SDL2_mixer_INCLUDE_DIR - SDL2_mixer_SDLMAIN_LIBRARY - SDL2_mixer_COCOA_LIBRARY - SDL2_mixer_MINGW_LIBRARY - SDL2_mixer_MWINDOWS_LIBRARY) - -find_package(SDL2 REQUIRED) -set_property(TARGET SDL2::SDL2_mixer APPEND PROPERTY - INTERFACE_LINK_LIBRARIES SDL2::SDL2) diff --git a/CMakeLists.txt b/CMakeLists.txt index d2d75e463..db8d9e23e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,10 @@ if(NIGHTLY_BUILD OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") set(CPACK ON) endif() +option(DEVILUTIONX_SYSTEM_SDL_AUDIOLIB "Use system-provided SDL_audiolib" OFF) +cmake_dependent_option(DEVILUTIONX_STATIC_SDL_AUDIOLIB "Link static SDL_audiolib" OFF + "DEVILUTIONX_SYSTEM_SDL_AUDIOLIB AND NOT DIST" ON) + option(DEVILUTIONX_SYSTEM_LIBSODIUM "Use system-provided libsodium" ON) cmake_dependent_option(DEVILUTIONX_STATIC_LIBSODIUM "Link static libsodium" OFF "DEVILUTIONX_SYSTEM_LIBSODIUM AND NOT DIST" ON) @@ -169,6 +173,12 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # for clang-tidy set(CMAKE_THREAD_PREFER_PTHREAD ON) set(THREADS_PREFER_PTHREAD_FLAG ON) +if(DEVILUTIONX_SYSTEM_SDL_AUDIOLIB) + find_package(SDL_audiolib REQUIRED) +else() + add_subdirectory(3rdParty/SDL_audiolib) +endif() + if(NOT N3DS) find_package(Threads REQUIRED) endif() @@ -197,7 +207,6 @@ endif() if(USE_SDL1) find_package(SDL REQUIRED) find_package(SDL_ttf REQUIRED) - find_package(SDL_mixer REQUIRED) include_directories(${SDL_INCLUDE_DIR}) else() find_package(SDL2 REQUIRED) @@ -218,7 +227,6 @@ else() add_library(SDL2::SDL2 ALIAS SDL2_lib) endif() find_package(SDL2_ttf REQUIRED) - find_package(SDL2_mixer REQUIRED) endif() add_library(smacker STATIC @@ -340,6 +348,7 @@ set(devilutionx_SRCS Source/utils/file_util.cpp Source/utils/language.cpp Source/utils/paths.cpp + Source/utils/push_aulib_decoder.cpp Source/utils/soundsample.cpp Source/utils/thread.cpp Source/DiabloUI/art.cpp @@ -539,6 +548,10 @@ foreach( def_name DEFAULT_WIDTH DEFAULT_HEIGHT + DEFAULT_AUDIO_SAMPLE_RATE + DEFAULT_AUDIO_CHANNELS + DEFAULT_AUDIO_BUFFER_SIZE + DEFAULT_AUDIO_RESAMPLING_QUALITY TTF_FONT_DIR TTF_FONT_NAME SDL1_VIDEO_MODE_BPP @@ -607,17 +620,17 @@ target_link_libraries(${BIN_TARGET} PUBLIC $<${UBSAN_GENEX}:-fsanitize=undefined if(USE_SDL1) target_link_libraries(${BIN_TARGET} PRIVATE ${SDL_TTF_LIBRARY} - ${SDL_MIXER_LIBRARY} ${SDL_LIBRARY}) target_compile_definitions(${BIN_TARGET} PRIVATE USE_SDL1) else() target_link_libraries(${BIN_TARGET} PRIVATE SDL2::SDL2 ${SDL2_MAIN} - SDL2::SDL2_ttf - SDL2::SDL2_mixer) + SDL2::SDL2_ttf) endif() +target_link_libraries(${BIN_TARGET} PRIVATE SDL_audiolib) + if(SWITCH) target_link_libraries(${BIN_TARGET} PRIVATE switch::libnx -lfreetype -lvorbisfile -lvorbis -logg -lmodplug -lmpg123 -lSDL2 -lopusfile -lopus -lEGL -lglapi -ldrm_nouveau -lpng -lbz2 -lz -lnx) @@ -820,9 +833,6 @@ if(CPACK) install(FILES "${PROJECT_SOURCE_DIR}/SDL2-2.0.14/i686-w64-mingw32/bin/SDL2.dll" DESTINATION "." ) - install(FILES "${PROJECT_SOURCE_DIR}/SDL2_mixer-2.0.4/i686-w64-mingw32/bin/SDL2_mixer.dll" - DESTINATION "." - ) install(FILES "${PROJECT_SOURCE_DIR}/SDL2_ttf-2.0.15/i686-w64-mingw32/bin/SDL2_ttf.dll" DESTINATION "." ) @@ -860,9 +870,6 @@ if(CPACK) install(FILES "${PROJECT_SOURCE_DIR}/SDL2-2.0.14/x86_64-w64-mingw32/bin/SDL2.dll" DESTINATION "." ) - install(FILES "${PROJECT_SOURCE_DIR}/SDL2_mixer-2.0.4/x86_64-w64-mingw32/bin/SDL2_mixer.dll" - DESTINATION "." - ) install(FILES "${PROJECT_SOURCE_DIR}/SDL2_ttf-2.0.15/x86_64-w64-mingw32/bin/SDL2_ttf.dll" DESTINATION "." ) @@ -924,9 +931,9 @@ if(CPACK) # -G DEB set(CPACK_PACKAGE_CONTACT "anders@jenbo.dk") if(USE_SDL1) - set(CPACK_DEBIAN_PACKAGE_DEPENDS "libsdl1.2debian, libsdl-mixer1.2, libsdl-ttf2.0-0") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "libsdl1.2debian, libsdl-ttf2.0-0") else() - set(CPACK_DEBIAN_PACKAGE_DEPENDS "libsdl2-2.0-0, libsdl2-mixer-2.0-0, libsdl2-ttf-2.0-0") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "libsdl2-2.0-0, libsdl2-ttf-2.0-0") endif() set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) diff --git a/Packaging/OpenDingux/build.sh b/Packaging/OpenDingux/build.sh index cfc0afda8..9e9825efa 100755 --- a/Packaging/OpenDingux/build.sh +++ b/Packaging/OpenDingux/build.sh @@ -66,7 +66,7 @@ prepare_buildroot() { make_buildroot() { cd "$BUILDROOT" - BR2_JLEVEL=0 make toolchain libzip sdl sdl_mixer sdl_ttf + BR2_JLEVEL=0 make toolchain libzip sdl sdl_ttf cd - } diff --git a/Packaging/amiga/prep.sh b/Packaging/amiga/prep.sh index ed52c81a5..78e8be130 100755 --- a/Packaging/amiga/prep.sh +++ b/Packaging/amiga/prep.sh @@ -38,16 +38,6 @@ cp -fvr libSDL.a ${SYSROOT}/usr/lib/ cp -fvr include/* ${SYSROOT}/usr/include/ cd .. -# SDL_mixer -wget https://github.com/SDL-mirror/SDL_mixer/archive/SDL-1.2.tar.gz -O SDL_mixer-SDL-1.2.tar.gz -tar -xvf SDL_mixer-SDL-1.2.tar.gz -cd SDL_mixer-SDL-1.2 -./autogen.sh -SDL_LIBS='-lSDL -ldebug' SDL_CFLAGS="-I${SYSROOT}/usr/include/SDL -noixemul" CFLAGS="${M68K_CFLAGS}" CXXFLAGS="${M68K_CXXFLAGS}" ./configure --disable-sdltest --disable-shared --enable-static --host=${TARGET} --prefix="${SYSROOT}/usr" -make -j$(getconf _NPROCESSORS_ONLN) -make install -cd .. - # FreeType wget https://download.savannah.gnu.org/releases/freetype/freetype-2.10.1.tar.gz -O freetype-2.10.1.tar.gz tar -xvf freetype-2.10.1.tar.gz diff --git a/Packaging/ctr/build.sh b/Packaging/ctr/build.sh index 701f3e986..f5cdbef81 100755 --- a/Packaging/ctr/build.sh +++ b/Packaging/ctr/build.sh @@ -26,7 +26,7 @@ build() { install_deps() { "$DEVKITPRO/pacman/bin/pacman" -S --needed --noconfirm --quiet \ - 3ds-sdl 3ds-sdl_ttf 3ds-sdl_mixer \ + 3ds-sdl 3ds-sdl_ttf \ 3ds-freetype 3ds-libogg 3ds-libvorbisidec 3ds-mikmod \ libctru citro3d picasso devkitARM general-tools 3dslink 3dstools devkitpro-pkgbuild-helpers } diff --git a/Packaging/debian/control b/Packaging/debian/control index a15c504be..a60aae571 100644 --- a/Packaging/debian/control +++ b/Packaging/debian/control @@ -8,7 +8,6 @@ Build-Depends: git, g++, gettext, - libsdl2-mixer-dev, libsdl2-ttf-dev, libsodium-dev Standards-Version: 4.3.0 @@ -23,8 +22,8 @@ Depends: ${misc:Depends}, devilutionx-data | diablo-data | game-data-packager (>= 40), Description: Diablo build for modern operating systems - Diablo by Blizzard Entertainment is undoubtedly one of - the best games of the 1990s + Diablo by Blizzard Entertainment is undoubtedly one of + the best games of the 1990s Package: diablo-data Architecture: all diff --git a/Packaging/fedora/devilutionx.spec b/Packaging/fedora/devilutionx.spec index 51cce91f0..c38959dc5 100644 --- a/Packaging/fedora/devilutionx.spec +++ b/Packaging/fedora/devilutionx.spec @@ -12,8 +12,8 @@ Source1: devilutionx.desktop Source2: deviltutionx-hellfire.desktop BuildRequires: cmake gcc gcc-c++ libstdc++-static glibc desktop-file-utils -BuildRequires: glibc-devel SDL2-devel SDL2_ttf-devel SDL2_mixer-devel libsodium-devel libasan -Requires: SDL2_ttf SDL2_mixer libsodium +BuildRequires: glibc-devel SDL2-devel SDL2_ttf-devel libsodium-devel libasan +Requires: SDL2_ttf libsodium %description Diablo I devolved - magic behind the 1996 computer game diff --git a/Packaging/switch/build.sh b/Packaging/switch/build.sh index 64ac2897e..54a2740b4 100755 --- a/Packaging/switch/build.sh +++ b/Packaging/switch/build.sh @@ -27,7 +27,7 @@ build() { install_deps() { "$DEVKITPRO/pacman/bin/pacman" -S --needed --noconfirm --quiet \ switch-freetype switch-mesa switch-glad switch-glm switch-sdl2 \ - switch-sdl2_ttf switch-sdl2_mixer switch-libvorbis switch-libmikmod switch-libsodium \ + switch-sdl2_ttf switch-libvorbis switch-libmikmod switch-libsodium \ libnx devkitA64 devkitA64 general-tools switch-tools devkitpro-pkgbuild-helpers } diff --git a/Packaging/windows/mingw-prep.sh b/Packaging/windows/mingw-prep.sh index 7781c96f7..dfabbf628 100755 --- a/Packaging/windows/mingw-prep.sh +++ b/Packaging/windows/mingw-prep.sh @@ -9,12 +9,11 @@ wget https://www.libsdl.org/release/SDL2-devel-2.0.14-mingw.tar.gz tar -xzf SDL2-devel-2.0.14-mingw.tar.gz wget https://www.libsdl.org/projects/SDL_ttf/release/SDL2_ttf-devel-2.0.15-mingw.tar.gz tar -xzf SDL2_ttf-devel-2.0.15-mingw.tar.gz -wget https://www.libsdl.org/projects/SDL_mixer/release/SDL2_mixer-devel-2.0.4-mingw.tar.gz -tar -xzf SDL2_mixer-devel-2.0.4-mingw.tar.gz +sudo cp -r SDL2*/i686-w64-mingw32 /usr + wget https://github.com/jedisct1/libsodium/releases/download/1.0.18-RELEASE/libsodium-1.0.18-mingw.tar.gz tar -xzf libsodium-1.0.18-mingw.tar.gz --no-same-owner sudo cp -r libsodium-win32/* /usr/i686-w64-mingw32 -sudo cp -r SDL2*/i686-w64-mingw32 /usr # Fixup pkgconfig prefix: find "${MINGW_PREFIX}/lib/pkgconfig/" -name '*.pc' -exec \ diff --git a/Packaging/windows/mingw-prep64.sh b/Packaging/windows/mingw-prep64.sh index 289d2cd78..ca1f167ca 100755 --- a/Packaging/windows/mingw-prep64.sh +++ b/Packaging/windows/mingw-prep64.sh @@ -9,12 +9,11 @@ wget https://www.libsdl.org/release/SDL2-devel-2.0.14-mingw.tar.gz tar -xzf SDL2-devel-2.0.14-mingw.tar.gz wget https://www.libsdl.org/projects/SDL_ttf/release/SDL2_ttf-devel-2.0.15-mingw.tar.gz tar -xzf SDL2_ttf-devel-2.0.15-mingw.tar.gz -wget https://www.libsdl.org/projects/SDL_mixer/release/SDL2_mixer-devel-2.0.4-mingw.tar.gz -tar -xzf SDL2_mixer-devel-2.0.4-mingw.tar.gz +sudo cp -r SDL2*/x86_64-w64-mingw32 /usr + wget https://github.com/jedisct1/libsodium/releases/download/1.0.18-RELEASE/libsodium-1.0.18-mingw.tar.gz tar -xzf libsodium-1.0.18-mingw.tar.gz --no-same-owner sudo cp -r libsodium-win64/* /usr/x86_64-w64-mingw32 -sudo cp -r SDL2*/x86_64-w64-mingw32 /usr # Fixup pkgconfig prefix: find "${MINGW_PREFIX}/lib/pkgconfig/" -name '*.pc' -exec \ diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 03fd41c5a..34e9d8733 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -62,6 +62,18 @@ namespace devilution { #ifndef DEFAULT_HEIGHT #define DEFAULT_HEIGHT 480 #endif +#ifndef DEFAULT_AUDIO_SAMPLE_RATE +#define DEFAULT_AUDIO_SAMPLE_RATE 22050 +#endif +#ifndef DEFAULT_AUDIO_CHANNELS +#define DEFAULT_AUDIO_CHANNELS 2 +#endif +#ifndef DEFAULT_AUDIO_BUFFER_SIZE +#define DEFAULT_AUDIO_BUFFER_SIZE 2048 +#endif +#ifndef DEFAULT_AUDIO_RESAMPLING_QUALITY +#define DEFAULT_AUDIO_RESAMPLING_QUALITY 5 +#endif SDL_Window *ghMainWnd; DWORD glSeedTbl[NUMLEVELS]; @@ -466,6 +478,10 @@ static void SaveOptions() setIniInt("Audio", "Walking Sound", sgOptions.Audio.bWalkingSound); setIniInt("Audio", "Auto Equip Sound", sgOptions.Audio.bAutoEquipSound); + setIniInt("Audio", "Sample Rate", sgOptions.Audio.nSampleRate); + setIniInt("Audio", "Channels", sgOptions.Audio.nChannels); + setIniInt("Audio", "Buffer Size", sgOptions.Audio.nBufferSize); + setIniInt("Audio", "Resampling Quality", sgOptions.Audio.nResamplingQuality); #ifndef __vita__ setIniInt("Graphics", "Width", sgOptions.Graphics.nWidth); setIniInt("Graphics", "Height", sgOptions.Graphics.nHeight); @@ -538,6 +554,11 @@ static void LoadOptions() sgOptions.Audio.bWalkingSound = getIniBool("Audio", "Walking Sound", true); sgOptions.Audio.bAutoEquipSound = getIniBool("Audio", "Auto Equip Sound", false); + sgOptions.Audio.nSampleRate = getIniInt("Audio", "Sample Rate", DEFAULT_AUDIO_SAMPLE_RATE); + sgOptions.Audio.nChannels = getIniInt("Audio", "Channels", DEFAULT_AUDIO_CHANNELS); + sgOptions.Audio.nBufferSize = getIniInt("Audio", "Buffer Size", DEFAULT_AUDIO_BUFFER_SIZE); + sgOptions.Audio.nResamplingQuality = getIniInt("Audio", "Resampling Quality", DEFAULT_AUDIO_RESAMPLING_QUALITY); + #ifndef __vita__ sgOptions.Graphics.nWidth = getIniInt("Graphics", "Width", DEFAULT_WIDTH); sgOptions.Graphics.nHeight = getIniInt("Graphics", "Height", DEFAULT_HEIGHT); @@ -673,6 +694,7 @@ static void diablo_deinit() if (was_snd_init) { effects_cleanup_sfx(); } + Aulib::quit(); if (was_ui_init) UiDestroy(); if (was_archives_init) diff --git a/Source/effects.cpp b/Source/effects.cpp index 6990b9269..20a1e06f9 100644 --- a/Source/effects.cpp +++ b/Source/effects.cpp @@ -3,8 +3,6 @@ * * Implementation of functions for loading and playing sounds. */ -#include - #include "init.h" #include "player.h" #include "sound.h" @@ -1306,7 +1304,12 @@ void PlaySfxLoc(_sfx_id psfx, int x, int y, bool randomizeByCategory) void sound_stop() { - Mix_HaltChannel(-1); + music_stop(); + for (auto &sfx : sgSFX) { + if (sfx.pSnd != nullptr && sfx.pSnd->DSB != nullptr) { + sfx.pSnd->DSB->Stop(); + } + } } void sound_update() diff --git a/Source/options.h b/Source/options.h index 7accc765a..c42fb8648 100644 --- a/Source/options.h +++ b/Source/options.h @@ -27,6 +27,15 @@ struct AudioOptions { bool bWalkingSound; /** @brief Automatically equipping items on pickup emits the equipment sound. */ bool bAutoEquipSound; + + /** @brief Output sample rate (Hz) */ + std::uint32_t nSampleRate; + /** @brief The number of output channels (1 or 2) */ + std::uint8_t nChannels; + /** @brief Buffer size (number of frames per channel) */ + std::uint32_t nBufferSize; + /** @brief Quality of the resampler, from 0 (lowest) to 10 (highest) */ + std::uint8_t nResamplingQuality; }; struct GraphicsOptions { diff --git a/Source/sound.cpp b/Source/sound.cpp index 1d6715649..229ef9685 100644 --- a/Source/sound.cpp +++ b/Source/sound.cpp @@ -3,15 +3,22 @@ * * Implementation of functions setting up the audio pipeline. */ +#include +#include + +#include +#include +#include +#include #include -#include #include "init.h" #include "options.h" -#include "storm/storm.h" #include "storm/storm_sdl_rw.h" -#include "utils/stubs.h" +#include "storm/storm.h" #include "utils/log.hpp" +#include "utils/stdcompat/optional.hpp" +#include "utils/stubs.h" namespace devilution { @@ -21,7 +28,7 @@ HANDLE sghMusic; namespace { -Mix_Music *music; +std::optional music; #ifdef DISABLE_STREAMING_MUSIC char *musicBuffer; @@ -133,11 +140,10 @@ TSnd *sound_file_load(const char *path, bool stream) } } else { DWORD dwBytes = SFileGetFileSize(file, nullptr); - BYTE *wave_file = DiabloAllocPtr(dwBytes); - SFileReadFile(file, wave_file, dwBytes, nullptr, nullptr); - error = pSnd->DSB->SetChunk(wave_file, dwBytes); + auto wave_file = std::make_unique(dwBytes); + SFileReadFile(file, wave_file.get(), dwBytes, nullptr, nullptr); + error = pSnd->DSB->SetChunk(std::move(wave_file), dwBytes); SFileCloseFile(file); - mem_free_dbg(wave_file); } if (error != 0) { ErrSdl(); @@ -171,22 +177,31 @@ void snd_init() sgOptions.Audio.nMusicVolume = CapVolume(sgOptions.Audio.nMusicVolume); gbMusicOn = sgOptions.Audio.nMusicVolume > VOLUME_MIN; - int result = Mix_OpenAudio(22050, AUDIO_S16LSB, 2, 1024); - if (result < 0) { - Log("{}", Mix_GetError()); + // Initialize the SDL_audiolib library. Set the output sample rate to + // 22kHz, the audio format to 16-bit signed, use 2 output channels + // (stereo), and a 2KiB output buffer. + if (!Aulib::init(sgOptions.Audio.nSampleRate, AUDIO_S16, sgOptions.Audio.nChannels, sgOptions.Audio.nBufferSize)) { + LogError(LogCategory::Audio, "Failed to initialize audio (Aulib::init): {}", SDL_GetError()); + return; } - Mix_AllocateChannels(25); - Mix_ReserveChannels(1); // reserve one channel for naration (SFileDda*) + LogVerbose(LogCategory::Audio, "Aulib sampleRate={} channels={} frameSize={} format={:#x}", + Aulib::sampleRate(), Aulib::channelCount(), Aulib::frameSize(), Aulib::sampleFormat()); gbSndInited = true; } +void snd_deinit() { + if (gbSndInited) { + Aulib::quit(); + } + + gbSndInited = false; +} + void music_stop() { - if (music != nullptr) { - Mix_HaltMusic(); - Mix_FreeMusic(music); - music = nullptr; + if (music) { + music = std::nullopt; #ifndef DISABLE_STREAMING_MUSIC SFileCloseFile(sghMusic); sghMusic = nullptr; @@ -227,9 +242,11 @@ void music_start(uint8_t nTrack) if (musicRw == nullptr) ErrSdl(); #endif - music = Mix_LoadMUSType_RW(musicRw, MUS_NONE, /*freesrc=*/1); - if (music == nullptr) { - Log("Mix_LoadMUSType_RW: {}", Mix_GetError()); + music.emplace(musicRw, std::make_unique(), + std::make_unique(sgOptions.Audio.nResamplingQuality), /*closeRw=*/true); + if (!music->open()) { + LogError(LogCategory::Audio, "Aulib::Stream::open (from music_start): {}", SDL_GetError()); + music = std::nullopt; #ifndef DISABLE_STREAMING_MUSIC SFileCloseFile(sghMusic); sghMusic = nullptr; @@ -241,11 +258,10 @@ void music_start(uint8_t nTrack) return; } - Mix_VolumeMusic(MIX_MAX_VOLUME - MIX_MAX_VOLUME * sgOptions.Audio.nMusicVolume / VOLUME_MIN); - if (Mix_PlayMusic(music, -1) < 0) { - Log("Mix_PlayMusic: {}", Mix_GetError()); - Mix_FreeMusic(music); - music = nullptr; + music->setVolume(1.F - static_cast(sgOptions.Audio.nMusicVolume) / VOLUME_MIN); + if (!music->play()) { + LogError(LogCategory::Audio, "Aulib::Stream::play (from music_start): {}", SDL_GetError()); + music = std::nullopt; #ifndef DISABLE_STREAMING_MUSIC SFileCloseFile(sghMusic); sghMusic = nullptr; @@ -278,8 +294,8 @@ int sound_get_or_set_music_volume(int volume) sgOptions.Audio.nMusicVolume = volume; - if (music != nullptr) - Mix_VolumeMusic(MIX_MAX_VOLUME - MIX_MAX_VOLUME * volume / VOLUME_MIN); + if (music) + music->setVolume(1.F - static_cast(sgOptions.Audio.nMusicVolume) / VOLUME_MIN); return sgOptions.Audio.nMusicVolume; } diff --git a/Source/sound.h b/Source/sound.h index efd4c7013..ab2aeeecb 100644 --- a/Source/sound.h +++ b/Source/sound.h @@ -44,7 +44,7 @@ void snd_play_snd(TSnd *pSnd, int lVolume, int lPan); TSnd *sound_file_load(const char *path, bool stream = false); void sound_file_cleanup(TSnd *sound_file); void snd_init(); -void sound_cleanup(); +void snd_deinit(); void music_stop(); void music_start(uint8_t nTrack); void sound_disable_music(bool disable); diff --git a/Source/storm/storm.cpp b/Source/storm/storm.cpp index 358a17216..85d611681 100644 --- a/Source/storm/storm.cpp +++ b/Source/storm/storm.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include diff --git a/Source/storm/storm_svid.cpp b/Source/storm/storm_svid.cpp index cb1b4331a..d40c25611 100644 --- a/Source/storm/storm_svid.cpp +++ b/Source/storm/storm_svid.cpp @@ -5,29 +5,29 @@ #include #include -#include #include +#include +#include #include "dx.h" #include "options.h" #include "palette.h" #include "storm/storm.h" #include "utils/display.h" +#include "utils/push_aulib_decoder.h" #include "utils/sdl_compat.h" #include "utils/log.hpp" -#if !SDL_VERSION_ATLEAST(2, 0, 4) -#include -#endif - namespace devilution { namespace { +std::optional SVidAudioStream; +PushAulibDecoder *SVidAudioDecoder; + unsigned long SVidWidth, SVidHeight; double SVidFrameEnd; double SVidFrameLength; -char SVidAudioDepth; -double SVidVolume; +std::uint8_t SVidAudioDepth; BYTE SVidLoop; smk SVidSMK; SDL_Color SVidPreviousPalette[256]; @@ -99,129 +99,9 @@ void TrySetVideoModeToSVidForSDL1() } #endif -#if !SDL_VERSION_ATLEAST(2, 0, 4) bool HaveAudio() { - return SDL_GetAudioStatus() != SDL_AUDIO_STOPPED; -} - -struct AudioQueueItem { - unsigned char *data; - unsigned long len; - const unsigned char *pos; -}; - -class AudioQueue { -public: - static void Callback(void *userdata, Uint8 *out, int out_len) - { - static_cast(userdata)->Dequeue(out, out_len); - } - - void Subscribe(SDL_AudioSpec *spec) - { - spec->userdata = this; - spec->callback = AudioQueue::Callback; - } - - void Enqueue(const unsigned char *data, unsigned long len) - { -#if SDL_VERSION_ATLEAST(2, 0, 4) - SDL_LockAudioDevice(deviceId); - EnqueueUnsafe(data, len); - SDL_UnlockAudioDevice(deviceId); -#else - SDL_LockAudio(); - EnqueueUnsafe(data, len); - SDL_UnlockAudio(); -#endif - } - - void Clear() - { - while (!queue_.empty()) - Pop(); - } - -private: - void EnqueueUnsafe(const unsigned char *data, unsigned long len) - { - AudioQueueItem item; - item.data = new unsigned char[len]; - memcpy(item.data, data, len * sizeof(item.data[0])); - item.len = len; - item.pos = item.data; - queue_.push(item); - } - - void Dequeue(Uint8 *out, int out_len) - { - SDL_memset(out, 0, sizeof(out[0]) * out_len); - AudioQueueItem *item; - while ((item = Next()) != NULL) { - if (static_cast(out_len) <= item->len) { - SDL_MixAudio(out, item->pos, out_len, SDL_MIX_MAXVOLUME); - item->pos += out_len; - item->len -= out_len; - return; - } - - SDL_MixAudio(out, item->pos, item->len, SDL_MIX_MAXVOLUME); - out += item->len; - out_len -= item->len; - Pop(); - } - } - - AudioQueueItem *Next() - { - while (!queue_.empty() && queue_.front().len == 0) - Pop(); - if (queue_.empty()) - return NULL; - return &queue_.front(); - } - - void Pop() - { - delete[] queue_.front().data; - queue_.pop(); - } - - std::queue queue_; -}; - -AudioQueue *sVidAudioQueue = new AudioQueue(); -#else // SDL_VERSION_ATLEAST(2, 0, 4) -SDL_AudioDeviceID deviceId; -bool HaveAudio() -{ - return deviceId != 0; -} -#endif - -void SVidRestartMixer() -{ - if (Mix_OpenAudio(22050, AUDIO_S16LSB, 2, 1024) < 0) { - Log("{}", Mix_GetError()); - } - Mix_AllocateChannels(25); - Mix_ReserveChannels(1); -} - -unsigned char *SVidApplyVolume(const unsigned char *raw, unsigned long rawLen) -{ - auto *scaled = (unsigned char *)malloc(rawLen); - - if (SVidAudioDepth == 16) { - for (unsigned long i = 0; i < rawLen / 2; i++) - ((Sint16 *)scaled)[i] = ((Sint16 *)raw)[i] * SVidVolume; - } else { - for (unsigned long i = 0; i < rawLen; i++) - scaled[i] = raw[i] * SVidVolume; - } - - return (unsigned char *)scaled; + return SVidAudioStream && SVidAudioStream->isPlaying(); } bool SVidLoadNextFrame() @@ -269,37 +149,34 @@ void SVidPlayBegin(const char *filename, int flags, HANDLE *video) return; } - unsigned char channels[7], depth[7]; - unsigned long rate[7]; + constexpr std::size_t MaxSmkChannels = 7; + unsigned char channels[MaxSmkChannels]; + unsigned char depth[MaxSmkChannels]; + unsigned long rate[MaxSmkChannels]; // NOLINT(google-runtime-int): Match `smk_info_audio` signature. smk_info_audio(SVidSMK, nullptr, channels, depth, rate); + LogVerbose(LogCategory::Audio, "SVid audio depth={} channels={} rate={}", depth[0], channels[0], rate[0]); + if (enableAudio && depth[0] != 0) { + sound_stop(); // Stop in-progress music and sound effects + smk_enable_audio(SVidSMK, 0, enableAudio); - SDL_AudioSpec audioFormat; - SDL_zero(audioFormat); - audioFormat.freq = rate[0]; - audioFormat.format = depth[0] == 16 ? AUDIO_S16SYS : AUDIO_U8; SVidAudioDepth = depth[0]; - audioFormat.channels = channels[0]; - - SVidVolume = sgOptions.Audio.nSoundVolume - VOLUME_MIN; - SVidVolume /= -VOLUME_MIN; - - Mix_CloseAudio(); - -#if SDL_VERSION_ATLEAST(2, 0, 4) - deviceId = SDL_OpenAudioDevice(nullptr, 0, &audioFormat, nullptr, 0); - if (deviceId == 0) { - ErrSdl(); + auto decoder = std::make_unique(channels[0], rate[0]); + SVidAudioDecoder = decoder.get(); + SVidAudioStream.emplace(/*rwops=*/nullptr, std::move(decoder), + std::make_unique(sgOptions.Audio.nResamplingQuality), /*closeRw=*/false); + const float volume = static_cast(sgOptions.Audio.nSoundVolume - VOLUME_MIN) / -VOLUME_MIN; + SVidAudioStream->setVolume(volume); + if (!SVidAudioStream->open()) { + LogError(LogCategory::Audio, "Aulib::Stream::open (from SVidPlayBegin): {}", SDL_GetError()); + SVidAudioStream = std::nullopt; + SVidAudioDecoder = nullptr; } - - SDL_PauseAudioDevice(deviceId, 0); /* start audio playing. */ -#else - sVidAudioQueue->Subscribe(&audioFormat); - if (SDL_OpenAudio(&audioFormat, NULL) != 0) { - ErrSdl(); + if (!SVidAudioStream->play()) { + LogError(LogCategory::Audio, "Aulib::Stream::play (from SVidPlayBegin): {}", SDL_GetError()); + SVidAudioStream = std::nullopt; + SVidAudioDecoder = nullptr; } - SDL_PauseAudio(0); -#endif } unsigned long nFrames; @@ -381,17 +258,13 @@ bool SVidPlayContinue() } if (HaveAudio()) { - unsigned long len = smk_get_audio_size(SVidSMK, 0); - unsigned char *audio = SVidApplyVolume(smk_get_audio(SVidSMK, 0), len); -#if SDL_VERSION_ATLEAST(2, 0, 4) - if (SDL_QueueAudio(deviceId, audio, len) <= -1) { - Log("{}", SDL_GetError()); - return false; + const auto len = smk_get_audio_size(SVidSMK, 0); + const unsigned char *buf = smk_get_audio(SVidSMK, 0); + if (SVidAudioDepth == 16) { + SVidAudioDecoder->PushSamples(reinterpret_cast(buf), len / 2); + } else { + SVidAudioDecoder->PushSamples(reinterpret_cast(buf), len); } -#else - sVidAudioQueue->Enqueue(audio, len); -#endif - free(audio); } if (SDL_GetTicks() * 1000 >= SVidFrameEnd) { @@ -463,15 +336,8 @@ bool SVidPlayContinue() void SVidPlayEnd(HANDLE video) { if (HaveAudio()) { -#if SDL_VERSION_ATLEAST(2, 0, 4) - SDL_ClearQueuedAudio(deviceId); - SDL_CloseAudioDevice(deviceId); - deviceId = 0; -#else - SDL_CloseAudio(); - sVidAudioQueue->Clear(); -#endif - SVidRestartMixer(); + SVidAudioStream = std::nullopt; + SVidAudioDecoder = nullptr; } if (SVidSMK != nullptr) diff --git a/Source/utils/push_aulib_decoder.cpp b/Source/utils/push_aulib_decoder.cpp new file mode 100644 index 000000000..53dd9621c --- /dev/null +++ b/Source/utils/push_aulib_decoder.cpp @@ -0,0 +1,105 @@ +#include "push_aulib_decoder.h" + +#include +#include +#include + +#include + +#include "appfat.h" + +namespace devilution { + +void PushAulibDecoder::PushSamples(const std::int16_t *data, unsigned size) noexcept +{ + AudioQueueItem item; + item.data.reset(new std::int16_t[size]); + std::memcpy(item.data.get(), data, size * sizeof(data[0])); + item.len = size; + item.pos = item.data.get(); + queue_.push(std::move(item)); +} + +void PushAulibDecoder::PushSamples(const std::uint8_t *data, unsigned size) noexcept +{ + AudioQueueItem item; + item.data.reset(new std::int16_t[size]); + constexpr std::int16_t Center = 128; + constexpr std::int16_t Scale = 256; + for (unsigned i = 0; i < size; ++i) + item.data[i] = static_cast((data[i] - Center) * Scale); + item.len = size; + item.pos = item.data.get(); + queue_.push(std::move(item)); +} + +void PushAulibDecoder::DiscardPendingSamples() noexcept +{ + SDLMutexLockGuard lock(queue_mutex_.get()); + queue_ = {}; +} + +bool PushAulibDecoder::open([[maybe_unused]] SDL_RWops *rwops) +{ + assert(rwops == nullptr); + return true; +} + +bool PushAulibDecoder::rewind() +{ + return false; +} + +std::chrono::microseconds PushAulibDecoder::duration() const +{ + return {}; +} + +bool PushAulibDecoder::seekToTime([[maybe_unused]] std::chrono::microseconds pos) +{ + return false; +} + +int PushAulibDecoder::doDecoding(float buf[], int len, bool &callAgain) +{ + callAgain = false; + + const auto writeFloats = [&buf](const std::int16_t *samples, unsigned count) { + constexpr float Scale = std::numeric_limits::max() + 1; + for (unsigned i = 0; i < count; ++i) { + buf[i] = static_cast(samples[i]) / Scale; + } + }; + + unsigned remaining = len; + { + SDLMutexLockGuard lock(queue_mutex_.get()); + AudioQueueItem *item; + while ((item = Next()) != nullptr) { + if (static_cast(remaining) <= item->len) { + writeFloats(item->pos, remaining); + item->pos += remaining; + item->len -= remaining; + return len; + } + + writeFloats(item->pos, item->len); + buf += item->len; + remaining -= static_cast(item->len); + queue_.pop(); + } + } + std::memset(buf, 0, remaining * sizeof(buf[0])); + return len; +} + +PushAulibDecoder::AudioQueueItem *PushAulibDecoder::Next() +{ + while (!queue_.empty() && queue_.front().len == 0) + queue_.pop(); + if (queue_.empty()) + return nullptr; + return &queue_.front(); +} + +} // namespace devilution diff --git a/Source/utils/push_aulib_decoder.h b/Source/utils/push_aulib_decoder.h new file mode 100644 index 000000000..250b465a7 --- /dev/null +++ b/Source/utils/push_aulib_decoder.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include "utils/sdl_mutex.h" + +namespace devilution { + +/** + * @brief A Decoder interface implementations that simply has the samples pushed into it by the user. + */ +class PushAulibDecoder final : public ::Aulib::Decoder { +public: + PushAulibDecoder(int numChannels, int sampleRate) + : numChannels_(numChannels) + , sampleRate_(sampleRate) + , queue_mutex_(SDL_CreateMutex()) + { + } + + void PushSamples(const std::uint8_t *data, unsigned size) noexcept; + void PushSamples(const std::int16_t *data, unsigned size) noexcept; + void DiscardPendingSamples() noexcept; + + bool open(SDL_RWops *rwops) override; + + [[nodiscard]] int getChannels() const override + { + return numChannels_; + } + + [[nodiscard]] int getRate() const override + { + return sampleRate_; + } + + bool rewind() override; + [[nodiscard]] std::chrono::microseconds duration() const override; + bool seekToTime(std::chrono::microseconds pos) override; + +protected: + int doDecoding(float buf[], int len, bool &callAgain) override; + +private: + struct AudioQueueItem { + std::unique_ptr data; + unsigned len; + const std::int16_t *pos; + }; + + const int numChannels_; + const int sampleRate_; + + // Requires holding the queue_mutex_. + AudioQueueItem *Next(); + + std::queue queue_; + SDLMutexUniquePtr queue_mutex_; +}; + +} // namespace devilution diff --git a/Source/utils/sdl_mutex.h b/Source/utils/sdl_mutex.h new file mode 100644 index 000000000..e55febea7 --- /dev/null +++ b/Source/utils/sdl_mutex.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include + +namespace devilution { + +/** + * @brief Deletes the SDL mutex using `SDL_DestroyMutex`. + */ +struct SDLMutexDeleter { + void operator()(SDL_mutex *mutex) const + { + SDL_DestroyMutex(mutex); + } +}; + +using SDLMutexUniquePtr = std::unique_ptr; + +struct SDLMutexLockGuard { +public: + explicit SDLMutexLockGuard(SDL_mutex *mutex) + : mutex_(mutex) + { + SDL_LockMutex(mutex_); + } + + ~SDLMutexLockGuard() + { + SDL_UnlockMutex(mutex_); + } + + SDLMutexLockGuard(const SDLMutexLockGuard &) = delete; + SDLMutexLockGuard(SDLMutexLockGuard &&) = delete; + SDLMutexLockGuard &operator=(const SDLMutexLockGuard &) = delete; + SDLMutexLockGuard &operator=(SDLMutexLockGuard &&) = delete; + +private: + SDL_mutex *mutex_; +}; + +} // namespace devilution diff --git a/Source/utils/soundsample.cpp b/Source/utils/soundsample.cpp index ad11f7234..ae53120aa 100644 --- a/Source/utils/soundsample.cpp +++ b/Source/utils/soundsample.cpp @@ -1,5 +1,10 @@ #include "utils/soundsample.h" +#include +#include + +#include +#include #include #ifdef USE_SDL1 #include "utils/sdl2_to_1_2_backports.h" @@ -7,6 +12,7 @@ #include "utils/sdl2_backports.h" #endif +#include "options.h" #include "storm/storm_sdl_rw.h" #include "utils/stubs.h" #include "utils/log.hpp" @@ -17,8 +23,8 @@ namespace devilution { void SoundSample::Release() { - Mix_FreeChunk(chunk); - chunk = nullptr; + stream_ = std::nullopt; + file_data_ = nullptr; }; /** @@ -26,17 +32,7 @@ void SoundSample::Release() */ bool SoundSample::IsPlaying() { - if (chunk == nullptr) - return false; - - int channels = Mix_AllocateChannels(-1); - for (int i = 0; i < channels; i++) { - if (Mix_GetChunk(i) == chunk && Mix_Playing(i) != 0) { - return true; - } - } - - return false; + return stream_ && stream_->isPlaying(); }; /** @@ -44,18 +40,21 @@ bool SoundSample::IsPlaying() */ void SoundSample::Play(int lVolume, int lPan, int channel) { - if (chunk == nullptr) + if (!stream_) return; - channel = Mix_PlayChannel(channel, chunk, 0); - if (channel == -1) { - Log("Too few channels, skipping sound"); + constexpr float Base = 10.F; + constexpr float Scale = 2000.F; + stream_->setVolume(std::pow(Base, static_cast(lVolume) / Scale)); + stream_->setStereoPosition( + lPan == 0 ? 0 + : copysign(1.F - std::pow(Base, static_cast(-std::fabs(lPan) / Scale)), + static_cast(lPan))); + + if (!stream_->play()) { + LogError(LogCategory::Audio, "Aulib::Stream::play (from SoundSample::Play): {}", SDL_GetError()); return; } - - Mix_Volume(channel, pow((double)10, (double)lVolume / 2000.0) * MIX_MAX_VOLUME); - int pan = copysign(pow((double)10, -abs(lPan) / 2000.0) * 255, (double)lPan); - Mix_SetPanning(channel, lPan > 0 ? pan : 255, lPan < 0 ? abs(pan) : 255); }; /** @@ -63,44 +62,36 @@ void SoundSample::Play(int lVolume, int lPan, int channel) */ void SoundSample::Stop() { - if (chunk == nullptr) - return; - - int channels = Mix_AllocateChannels(-1); - for (int i = 0; i < channels; i++) { - if (Mix_GetChunk(i) != chunk) { - continue; - } - - Mix_HaltChannel(i); - } + if (stream_) + stream_->stop(); }; int SoundSample::SetChunkStream(HANDLE stormHandle) { - chunk = Mix_LoadWAV_RW(SFileRw_FromStormHandle(stormHandle), /*freesrc=*/1); - if (chunk == nullptr) { - Log("Mix_LoadWAV_RW: {}", Mix_GetError()); + stream_.emplace(SFileRw_FromStormHandle(stormHandle), std::make_unique(), + std::make_unique(sgOptions.Audio.nResamplingQuality), /*closeRw=*/true); + if (!stream_->open()) { + stream_ = std::nullopt; + LogError(LogCategory::Audio, "Aulib::Stream::open (from SoundSample::SetChunkStream): {}", SDL_GetError()); return -1; } return 0; } -/** - * @brief This can load WAVE, AIFF, RIFF, OGG, and VOC formats - * @param fileData Buffer containing file data - * @param dwBytes Length of buffer - * @return 0 on success, -1 otherwise - */ -int SoundSample::SetChunk(BYTE *fileData, DWORD dwBytes) +int SoundSample::SetChunk(std::unique_ptr fileData, DWORD dwBytes) { - SDL_RWops *buf1 = SDL_RWFromConstMem(fileData, dwBytes); - if (buf1 == nullptr) { + file_data_ = std::move(fileData); + SDL_RWops *buf = SDL_RWFromConstMem(file_data_.get(), dwBytes); + if (buf == nullptr) { return -1; } - chunk = Mix_LoadWAV_RW(buf1, 1); - if (chunk == nullptr) { + stream_.emplace(buf, std::make_unique(), + std::make_unique(sgOptions.Audio.nResamplingQuality), /*closeRw=*/true); + if (!stream_->open()) { + stream_ = std::nullopt; + file_data_ = nullptr; + LogError(LogCategory::Audio, "Aulib::Stream::open (from SoundSample::SetChunkStream): {}", SDL_GetError()); return -1; } @@ -112,21 +103,9 @@ int SoundSample::SetChunk(BYTE *fileData, DWORD dwBytes) */ int SoundSample::GetLength() { - if (chunk == nullptr) + if (!stream_) return 0; - - int frequency, channels; - Uint16 format; - Mix_QuerySpec(&frequency, &format, &channels); - - int bytePerSample = 2; - if (format == AUDIO_U8 || format == AUDIO_S8) { - bytePerSample = 1; - } - - uint64_t ms = 1000; // milliseconds, 64bit to avoid overflow when multiplied by alen - int bps = frequency * channels * bytePerSample; // bytes per second - return (int)(chunk->alen * ms / bps); + return std::chrono::duration_cast(stream_->duration()).count(); }; } // namespace devilution diff --git a/Source/utils/soundsample.h b/Source/utils/soundsample.h index 1bf38ac5d..7192627ba 100644 --- a/Source/utils/soundsample.h +++ b/Source/utils/soundsample.h @@ -1,8 +1,12 @@ #pragma once -#include +#include +#include + +#include #include "miniwin/miniwin.h" +#include "utils/stdcompat/optional.hpp" namespace devilution { @@ -13,11 +17,20 @@ public: void Play(int lVolume, int lPan, int channel = -1); void Stop(); int SetChunkStream(HANDLE stormHandle); - int SetChunk(BYTE *fileData, DWORD dwBytes); + + /** + * @brief Sets the sample's WAV, FLAC, or Ogg/Vorbis data. + * @param fileData Buffer containing the data + * @param dwBytes Length of buffer + * @return 0 on success, -1 otherwise + */ + int SetChunk(std::unique_ptr file_data, DWORD dwBytes); + int GetLength(); private: - Mix_Chunk *chunk; + std::unique_ptr file_data_; + std::optional stream_; }; } // namespace devilution diff --git a/Source/utils/stdcompat/optional.hpp b/Source/utils/stdcompat/optional.hpp index cde1efc04..2c51da0db 100644 --- a/Source/utils/stdcompat/optional.hpp +++ b/Source/utils/stdcompat/optional.hpp @@ -1,14 +1,15 @@ -#pragma once - -#ifdef __has_include -#if defined(__cplusplus) && __cplusplus >= 201606L && __has_include() -#include // IWYU pragma: export -#elif __has_include() -#include // IWYU pragma: export -#define optional experimental::optional -#else -#error "Missing support for or " -#endif -#else -#error "__has_include unavailable" -#endif +#pragma once + +#ifdef __has_include +#if defined(__cplusplus) && __cplusplus >= 201606L && __has_include() +#include // IWYU pragma: export +#elif __has_include() +#include // IWYU pragma: export +#define optional experimental::optional +#define nullopt experimental::nullopt +#else +#error "Missing support for or " +#endif +#else +#error "__has_include unavailable" +#endif diff --git a/appveyor.yml b/appveyor.yml index a9f9f1b4d..3daf85929 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,7 +10,7 @@ install: - git pull - .\bootstrap-vcpkg.bat - cd %APPVEYOR_BUILD_FOLDER% - - vcpkg install --recurse fmt:x64-windows sdl2:x64-windows sdl2-mixer:x64-windows sdl2-ttf:x64-windows libsodium:x64-windows + - vcpkg install --recurse fmt:x64-windows sdl2:x64-windows sdl2-ttf:x64-windows libsodium:x64-windows before_build: - cmake -G "Visual Studio 16 2019" -A x64 -DNIGHTLY_BUILD=ON -DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake . diff --git a/docs/building.md b/docs/building.md index 3941f8968..79af01895 100644 --- a/docs/building.md +++ b/docs/building.md @@ -84,7 +84,7 @@ cmake --build . -j $(sysctl -n hw.ncpuonline) Download and place the 32bit MinGW Development Libraries of [SDL2](https://www.libsdl.org/download-2.0.php), [SDL2_mixer](https://www.libsdl.org/projects/SDL_mixer/), [SDL2_ttf](https://www.libsdl.org/projects/SDL_ttf/) and [Libsodium](https://github.com/jedisct1/libsodium/releases) in `/usr/i686-w64-mingw32`. This can be done automatically by running `Packaging/windows/mingw-prep.sh`. ``` -sudo apt-get install cmake gcc-mingw-w64-i686 g++-mingw-w64-i686 +sudo apt-get install cmake gcc-mingw-w64-i686 g++-mingw-w64-i686 pkg-config-mingw-w64-i686 ``` ### 64-bit @@ -92,7 +92,7 @@ sudo apt-get install cmake gcc-mingw-w64-i686 g++-mingw-w64-i686 Download and place the 64bit MinGW Development Libraries of [SDL2](https://www.libsdl.org/download-2.0.php), [SDL2_mixer](https://www.libsdl.org/projects/SDL_mixer/), [SDL2_ttf](https://www.libsdl.org/projects/SDL_ttf/) and [Libsodium](https://github.com/jedisct1/libsodium/releases) in `/usr/x86_64-w64-mingw32`. This can be done automatically by running `Packaging/windows/mingw-prep64.sh`. ``` -sudo apt-get install cmake gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 +sudo apt-get install cmake gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 pkg-config-mingw-w64-x86-64 ``` ### Compiling @@ -186,7 +186,7 @@ https://devkitpro.org/wiki/Getting_Started - Install required packages with (dkp-)pacman: ``` sudo (dkp-)pacman -S devkitARM general-tools 3dstools devkitpro-pkgbuild-helpers \ - libctru citro3d 3ds-sdl 3ds-sdl_ttf 3ds-sdl_mixer \ + libctru citro3d 3ds-sdl 3ds-sdl_ttf \ 3ds-freetype 3ds-libogg 3ds-libvorbisidec 3ds-mikmod ``` - Download or compile [bannertool](https://github.com/Steveice10/bannertool/releases) and [makerom](https://github.com/jakcron/Project_CTR/releases) diff --git a/docs/installing.md b/docs/installing.md index 26cd44da1..f2433e4dc 100644 --- a/docs/installing.md +++ b/docs/installing.md @@ -17,9 +17,9 @@ Download the latest [DevilutionX release](https://github.com/diasurgical/devilut
Linux - Copy the MPQ files to the folder containing the DevilutionX executable, or to the data folder. The data folder path may differ depending on distro, version, and security settings, but will normally be `~/.local/share/diasurgical/devilution/` -- Install [SDL2](https://www.libsdl.org/download-2.0.php), [SDL2_mixer](https://www.libsdl.org/projects/SDL_mixer/) and [SDL2_ttf](https://www.libsdl.org/projects/SDL_ttf/): - - Ubuntu/Debian/Rasbian `sudo apt install libsdl2-2.0-0 libsdl2-ttf-2.0-0 libsdl2-mixer-2.0-0` - - Fedora `sudo dnf install SDL2 SDL2_ttf SDL2_mixer` +- Install [SDL2](https://www.libsdl.org/download-2.0.php) and [SDL2_ttf](https://www.libsdl.org/projects/SDL_ttf/): + - Ubuntu/Debian/Rasbian `sudo apt install libsdl2-2.0-0 libsdl2-ttf-2.0-0` + - Fedora `sudo dnf install SDL2 SDL2_ttf` - Run `./devilutionx`
@@ -117,7 +117,7 @@ sudo apt install devilutionx - Copy [devilutionx-rg350.opk](https://github.com/diasurgical/devilutionX/releases/latest/download/devilutionx-rg350.opk) to `/media/sdcard/APPS/`. - Copy the MPQ files to `/media/home/.local/share/diasurgical/devilution/` -- +- **NOTE:** You can copy the MPQ files to sdcard instead and create a symlink at the expected location. To do this, SSH into your RG350 and run: ~~~bash