Browse Source

🎉 Switch from SDL_mixer to SDL_audiolib

SDL_mixer can only stream a single music track
SDL_audiolib has unlimited streams.

With this change, we finally have streaming sounds (respecting
sfx_STREAM).

Audio options can now also be set via diablo.ini, which should help us
better diagnose the static noise issues.
pull/1595/head
Gleb Mazovetskiy 5 years ago
parent
commit
acee2ef14c
  1. 8
      .circleci/config.yml
  2. 6
      .editorconfig
  3. 35
      3rdParty/SDL_audiolib/CMakeLists.txt
  4. 1
      Brewfile
  5. 197
      CMake/FindSDL2_mixer.cmake
  6. 33
      CMakeLists.txt
  7. 2
      Packaging/OpenDingux/build.sh
  8. 10
      Packaging/amiga/prep.sh
  9. 2
      Packaging/ctr/build.sh
  10. 5
      Packaging/debian/control
  11. 4
      Packaging/fedora/devilutionx.spec
  12. 2
      Packaging/switch/build.sh
  13. 5
      Packaging/windows/mingw-prep.sh
  14. 5
      Packaging/windows/mingw-prep64.sh
  15. 22
      Source/diablo.cpp
  16. 9
      Source/effects.cpp
  17. 9
      Source/options.h
  18. 70
      Source/sound.cpp
  19. 2
      Source/sound.h
  20. 1
      Source/storm/storm.cpp
  21. 210
      Source/storm/storm_svid.cpp
  22. 105
      Source/utils/push_aulib_decoder.cpp
  23. 67
      Source/utils/push_aulib_decoder.h
  24. 43
      Source/utils/sdl_mutex.h
  25. 99
      Source/utils/soundsample.cpp
  26. 19
      Source/utils/soundsample.h
  27. 29
      Source/utils/stdcompat/optional.hpp
  28. 2
      appveyor.yml
  29. 6
      docs/building.md
  30. 8
      docs/installing.md

8
.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}

6
.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

35
3rdParty/SDL_audiolib/CMakeLists.txt vendored

@ -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)

1
Brewfile

@ -1,6 +1,5 @@
brew "cmake"
brew "fmt"
brew "sdl2_mixer"
brew "sdl2_ttf"
brew "libsodium"
brew "pkg-config"

197
CMake/FindSDL2_mixer.cmake

@ -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 <ryan.pavlik@gmail.com> <abiryan@ryand.net>
#
# 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)

33
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)

2
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 -
}

10
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

2
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
}

5
Packaging/debian/control vendored

@ -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

4
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

2
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
}

5
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 \

5
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 \

22
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)

9
Source/effects.cpp

@ -3,8 +3,6 @@
*
* Implementation of functions for loading and playing sounds.
*/
#include <SDL_mixer.h>
#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()

9
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 {

70
Source/sound.cpp

@ -3,15 +3,22 @@
*
* Implementation of functions setting up the audio pipeline.
*/
#include <cstdint>
#include <memory>
#include <aulib.h>
#include <Aulib/DecoderDrwav.h>
#include <Aulib/Stream.h>
#include <Aulib/ResamplerSpeex.h>
#include <SDL.h>
#include <SDL_mixer.h>
#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<Aulib::Stream> 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<std::uint8_t[]>(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<Aulib::DecoderDrwav>(),
std::make_unique<Aulib::ResamplerSpeex>(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<float>(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<float>(sgOptions.Audio.nMusicVolume) / VOLUME_MIN);
return sgOptions.Audio.nMusicVolume;
}

2
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);

1
Source/storm/storm.cpp

@ -2,7 +2,6 @@
#include <SDL.h>
#include <SDL_endian.h>
#include <SDL_mixer.h>
#include <cstddef>
#include <cstdint>
#include <string>

210
Source/storm/storm_svid.cpp

@ -5,29 +5,29 @@
#include <cstring>
#include <SDL.h>
#include <SDL_mixer.h>
#include <smacker.h>
#include <Aulib/Stream.h>
#include <Aulib/ResamplerSpeex.h>
#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 <queue>
#endif
namespace devilution {
namespace {
std::optional<Aulib::Stream> 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<AudioQueue *>(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<unsigned long>(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<AudioQueueItem> 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<PushAulibDecoder>(channels[0], rate[0]);
SVidAudioDecoder = decoder.get();
SVidAudioStream.emplace(/*rwops=*/nullptr, std::move(decoder),
std::make_unique<Aulib::ResamplerSpeex>(sgOptions.Audio.nResamplingQuality), /*closeRw=*/false);
const float volume = static_cast<float>(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<const std::int16_t *>(buf), len / 2);
} else {
SVidAudioDecoder->PushSamples(reinterpret_cast<const std::uint8_t *>(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)

105
Source/utils/push_aulib_decoder.cpp

@ -0,0 +1,105 @@
#include "push_aulib_decoder.h"
#include <algorithm>
#include <cstring>
#include <limits>
#include <aulib.h>
#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<std::int16_t>((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<std::int16_t>::max() + 1;
for (unsigned i = 0; i < count; ++i) {
buf[i] = static_cast<float>(samples[i]) / Scale;
}
};
unsigned remaining = len;
{
SDLMutexLockGuard lock(queue_mutex_.get());
AudioQueueItem *item;
while ((item = Next()) != nullptr) {
if (static_cast<unsigned>(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<int>(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

67
Source/utils/push_aulib_decoder.h

@ -0,0 +1,67 @@
#pragma once
#include <chrono>
#include <cstdint>
#include <memory>
#include <queue>
#include <Aulib/Decoder.h>
#include <SDL_mutex.h>
#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<std::int16_t[]> data;
unsigned len;
const std::int16_t *pos;
};
const int numChannels_;
const int sampleRate_;
// Requires holding the queue_mutex_.
AudioQueueItem *Next();
std::queue<AudioQueueItem> queue_;
SDLMutexUniquePtr queue_mutex_;
};
} // namespace devilution

43
Source/utils/sdl_mutex.h

@ -0,0 +1,43 @@
#pragma once
#include <memory>
#include <SDL_mutex.h>
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<SDL_mutex, SDLMutexDeleter>;
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

99
Source/utils/soundsample.cpp

@ -1,5 +1,10 @@
#include "utils/soundsample.h"
#include <cmath>
#include <chrono>
#include <Aulib/DecoderDrwav.h>
#include <Aulib/ResamplerSpeex.h>
#include <SDL.h>
#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<float>(lVolume) / Scale));
stream_->setStereoPosition(
lPan == 0 ? 0
: copysign(1.F - std::pow(Base, static_cast<float>(-std::fabs(lPan) / Scale)),
static_cast<float>(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<Aulib::DecoderDrwav>(),
std::make_unique<Aulib::ResamplerSpeex>(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<std::uint8_t[]> 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<Aulib::DecoderDrwav>(),
std::make_unique<Aulib::ResamplerSpeex>(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<std::chrono::milliseconds>(stream_->duration()).count();
};
} // namespace devilution

19
Source/utils/soundsample.h

@ -1,8 +1,12 @@
#pragma once
#include <SDL_mixer.h>
#include <cstdint>
#include <memory>
#include <Aulib/Stream.h>
#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<std::uint8_t[]> file_data, DWORD dwBytes);
int GetLength();
private:
Mix_Chunk *chunk;
std::unique_ptr<std::uint8_t[]> file_data_;
std::optional<Aulib::Stream> stream_;
};
} // namespace devilution

29
Source/utils/stdcompat/optional.hpp

@ -1,14 +1,15 @@
#pragma once
#ifdef __has_include
#if defined(__cplusplus) && __cplusplus >= 201606L && __has_include(<optional>)
#include <optional> // IWYU pragma: export
#elif __has_include(<experimental/optional>)
#include <experimental/optional> // IWYU pragma: export
#define optional experimental::optional
#else
#error "Missing support for <optional> or <experimental/optional>"
#endif
#else
#error "__has_include unavailable"
#endif
#pragma once
#ifdef __has_include
#if defined(__cplusplus) && __cplusplus >= 201606L && __has_include(<optional>)
#include <optional> // IWYU pragma: export
#elif __has_include(<experimental/optional>)
#include <experimental/optional> // IWYU pragma: export
#define optional experimental::optional
#define nullopt experimental::nullopt
#else
#error "Missing support for <optional> or <experimental/optional>"
#endif
#else
#error "__has_include unavailable"
#endif

2
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 .

6
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)

8
docs/installing.md

@ -17,9 +17,9 @@ Download the latest [DevilutionX release](https://github.com/diasurgical/devilut
<details><summary>Linux</summary>
- 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`
</details>
@ -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

Loading…
Cancel
Save