mirror of
https://github.com/SFML/SFML.git
synced 2024-11-28 14:21:04 +08:00
Use 'atexit' registration
This commit is contained in:
commit
7229a6de15
121
.github/workflows/ci.yml
vendored
121
.github/workflows/ci.yml
vendored
@ -9,6 +9,7 @@ concurrency:
|
||||
env:
|
||||
DISPLAY: ":99" # Display number to use for the X server
|
||||
GALLIUM_DRIVER: llvmpipe # Use Mesa 3D software OpenGL renderer
|
||||
ANDROID_NDK_VERSION: "26.1.10909125" # Android NDK version to use
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@ -35,13 +36,13 @@ jobs:
|
||||
- { name: Windows VS2022 Unity, os: windows-2022, flags: -DSFML_USE_MESA3D=TRUE -DCMAKE_UNITY_BUILD=ON -GNinja }
|
||||
- { name: Windows LLVM/Clang, os: windows-2022, flags: -DSFML_USE_MESA3D=TRUE -DCMAKE_CXX_COMPILER=clang++ -GNinja }
|
||||
- { name: Windows MinGW, os: windows-2022, flags: -DSFML_USE_MESA3D=TRUE -DCMAKE_CXX_COMPILER=g++ -GNinja }
|
||||
- { name: Linux GCC, os: ubuntu-22.04, flags: -DSFML_RUN_DISPLAY_TESTS=ON -GNinja }
|
||||
- { name: Linux Clang, os: ubuntu-22.04, flags: -DCMAKE_CXX_COMPILER=clang++ -DSFML_RUN_DISPLAY_TESTS=ON -GNinja , gcovr_options: '--gcov-executable="llvm-cov-$CLANG_VERSION gcov"' }
|
||||
- { name: Linux GCC, os: ubuntu-22.04, flags: -GNinja }
|
||||
- { name: Linux Clang, os: ubuntu-22.04, flags: -DCMAKE_CXX_COMPILER=clang++ -GNinja , gcovr_options: '--gcov-executable="llvm-cov-$CLANG_VERSION gcov"' }
|
||||
- { name: Linux GCC DRM, os: ubuntu-22.04, flags: -DSFML_USE_DRM=ON -DSFML_RUN_DISPLAY_TESTS=OFF -GNinja }
|
||||
- { name: Linux GCC OpenGL ES, os: ubuntu-22.04, flags: -DSFML_OPENGL_ES=ON -DSFML_RUN_DISPLAY_TESTS=OFF -GNinja }
|
||||
- { name: macOS x64, os: macos-12, flags: -GNinja }
|
||||
- { name: macOS x64 Xcode, os: macos-12, flags: -GXcode }
|
||||
- { name: macOS arm64, os: macos-14, flags: -GNinja }
|
||||
- { name: macOS arm64, os: macos-14, flags: -GNinja -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF }
|
||||
- { name: iOS, os: macos-12, flags: -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_ARCHITECTURES=arm64 }
|
||||
- { name: iOS Xcode, os: macos-12, flags: -DCMAKE_SYSTEM_NAME=iOS -GXcode -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=NO }
|
||||
config:
|
||||
@ -55,29 +56,56 @@ jobs:
|
||||
- platform: { name: Windows VS2022 x64, os: windows-2022 }
|
||||
config: { name: Static with PCH (MSVC), flags: -DSFML_USE_MESA3D=TRUE -GNinja -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 }
|
||||
- platform: { name: Linux GCC, os: ubuntu-22.04 }
|
||||
config: { name: Static with PCH (GCC), flags: -DSFML_RUN_DISPLAY_TESTS=ON -GNinja -DCMAKE_CXX_COMPILER=g++ -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 }
|
||||
config: { name: Static with PCH (GCC), flags: -GNinja -DCMAKE_CXX_COMPILER=g++ -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 }
|
||||
- platform: { name: Linux Clang, os: ubuntu-22.04 }
|
||||
config: { name: Static with PCH (Clang), flags: -DSFML_RUN_DISPLAY_TESTS=ON -GNinja -DCMAKE_CXX_COMPILER=clang++ -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 }
|
||||
config: { name: Static with PCH (Clang), flags: -GNinja -DCMAKE_CXX_COMPILER=clang++ -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 }
|
||||
- platform: { name: Windows MinGW, os: windows-2022 }
|
||||
config: { name: Static Standard Libraries, flags: -GNinja -DSFML_USE_MESA3D=TRUE -DCMAKE_CXX_COMPILER=g++ -DSFML_USE_STATIC_STD_LIBS=TRUE }
|
||||
- platform: { name: Windows MinGW, os: windows-2022 }
|
||||
config: { name: Static with PCH (GCC), flags: -GNinja -DSFML_USE_MESA3D=TRUE -DCMAKE_CXX_COMPILER=g++ -DBUILD_SHARED_LIBS=FALSE -DSFML_ENABLE_PCH=1 }
|
||||
- platform: { name: macOS, os: macos-12 }
|
||||
config: { name: Frameworks, flags: -GNinja -DSFML_BUILD_FRAMEWORKS=TRUE -DBUILD_SHARED_LIBS=TRUE }
|
||||
- platform: { name: Android, os: ubuntu-22.04 }
|
||||
config: { name: x86 (API 21), flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=x86 -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=21 -DCMAKE_ANDROID_NDK=$ANDROID_SDK_ROOT/ndk/26.1.10909125 -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared, arch: x86, api: 21 }
|
||||
type: { name: Release }
|
||||
- platform: { name: Android, os: ubuntu-22.04 }
|
||||
config: { name: x86_64 (API 24), flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=x86_64 -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=24 -DCMAKE_ANDROID_NDK=$ANDROID_SDK_ROOT/ndk/26.1.10909125 -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared, arch: x86_64, api: 24 }
|
||||
type: { name: Release }
|
||||
- platform: { name: Android, os: ubuntu-22.04 }
|
||||
config: { name: armeabi-v7a (API 29), flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=29 -DCMAKE_ANDROID_NDK=$ANDROID_SDK_ROOT/ndk/26.1.10909125 -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared, arch: armeabi-v7a, api: 29 }
|
||||
type: { name: Debug, flags: -DCMAKE_BUILD_TYPE=Debug }
|
||||
- platform: { name: Android, os: ubuntu-22.04 }
|
||||
config: { name: arm64-v8a (API 33), flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=arm64-v8a -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=33 -DCMAKE_ANDROID_NDK=$ANDROID_SDK_ROOT/ndk/26.1.10909125 -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared, arch: arm64-v8a, api: 33 }
|
||||
type: { name: Debug, flags: -DCMAKE_BUILD_TYPE=Debug }
|
||||
- platform: { name: macOS , os: macos-12 }
|
||||
config: { name: System Deps, flags: -GNinja -DBUILD_SHARED_LIBS=TRUE -DSFML_USE_SYSTEM_DEPS=TRUE }
|
||||
- platform: { name: Android, os: ubuntu-latest }
|
||||
config:
|
||||
name: x86 (API 21)
|
||||
flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=x86 -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=21 -DCMAKE_ANDROID_NDK=$ANDROID_NDK_ROOT -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF
|
||||
arch: x86
|
||||
api: 21
|
||||
libcxx: i686-linux-android/libc++_shared.so
|
||||
emuarch: x86
|
||||
# emuapi: 29 # Removing this causes the tests to not run. This works around an issue that causes the test step to hang indefinitely.
|
||||
type: { name: Release }
|
||||
- platform: { name: Android, os: ubuntu-latest }
|
||||
config:
|
||||
name: x86_64 (API 24)
|
||||
flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=x86_64 -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=24 -DCMAKE_ANDROID_NDK=$ANDROID_NDK_ROOT -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF
|
||||
arch: x86_64
|
||||
api: 24
|
||||
libcxx: x86_64-linux-android/libc++_shared.so
|
||||
emuarch: x86_64
|
||||
# emuapi: 34 # Removing this causes the tests to not run. This works around an issue that causes the Network module tests to fail.
|
||||
type: { name: Release }
|
||||
- platform: { name: Android, os: ubuntu-latest }
|
||||
config:
|
||||
name: armeabi-v7a (API 29)
|
||||
flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=29 -DCMAKE_ANDROID_NDK=$ANDROID_NDK_ROOT -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared
|
||||
arch: armeabi-v7a
|
||||
api: 29
|
||||
# There are no emulators available for armeabi-v7a so we skip running the tests (we still build them) by not specifying emuapi
|
||||
type: { name: Debug, flags: -DCMAKE_BUILD_TYPE=Debug }
|
||||
- platform: { name: Android, os: ubuntu-latest }
|
||||
config:
|
||||
name: arm64-v8a (API 33)
|
||||
flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=arm64-v8a -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=33 -DCMAKE_ANDROID_NDK=$ANDROID_NDK_ROOT -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_RUN_AUDIO_DEVICE_TESTS=OFF
|
||||
arch: arm64-v8a
|
||||
api: 33
|
||||
libcxx: aarch64-linux-android/libc++_shared.so
|
||||
emuarch: arm64-v8a
|
||||
emuapi: 27
|
||||
emuflags: -qemu -machine virt
|
||||
type: { name: Debug, flags: -DCMAKE_BUILD_TYPE=Debug }
|
||||
|
||||
|
||||
steps:
|
||||
@ -107,11 +135,21 @@ jobs:
|
||||
echo "CLANG_VERSION=$CLANG_VERSION" >> $GITHUB_ENV
|
||||
sudo apt-get update && sudo apt-get install xorg-dev libxrandr-dev libxcursor-dev libudev-dev libflac-dev libvorbis-dev libgl1-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev xvfb fluxbox ccache gcovr ${{ matrix.platform.name == 'Linux Clang' && 'llvm-$CLANG_VERSION' || '' }}
|
||||
|
||||
- name: Remove ALSA Library
|
||||
if: runner.os == 'Linux' && matrix.platform.name != 'Android'
|
||||
run: sudo apt-get remove -y libasound2
|
||||
|
||||
# LIBCXX_SHARED_SO is the path used by CMake to copy the necessary runtime library to the AVD
|
||||
# We find it by searching ANDROID_NDK_ROOT for file paths ending with matrix.config.libcxx
|
||||
- name: Install Android Components
|
||||
if: matrix.platform.name == 'Android'
|
||||
run: |
|
||||
echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "build-tools;33.0.2"
|
||||
echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "ndk;26.1.10909125"
|
||||
ANDROID_NDK_ROOT=$(echo $ANDROID_SDK_ROOT/ndk/$ANDROID_NDK_VERSION)
|
||||
echo "ANDROID_NDK_ROOT=$ANDROID_NDK_ROOT" >> $GITHUB_ENV
|
||||
LIBCXX_SHARED_SO=$(find $ANDROID_NDK_ROOT -path \*/${{ matrix.config.libcxx }})
|
||||
echo "LIBCXX_SHARED_SO=$LIBCXX_SHARED_SO" >> $GITHUB_ENV
|
||||
|
||||
- name: Install macOS Tools
|
||||
if: runner.os == 'macOS'
|
||||
@ -197,11 +235,11 @@ jobs:
|
||||
# Make use of a test to print OpenGL vendor/renderer/version info to the console
|
||||
find build/bin -name test-sfml-window -or -name test-sfml-window.exe -exec sh -c "{} *sf::Context* --section=\"Version String\" --success | grep OpenGL" \;
|
||||
|
||||
- name: Test
|
||||
- name: Test (Windows)
|
||||
if: runner.os == 'Windows' && !contains(matrix.platform.name, 'MinGW')
|
||||
run: cmake --build build --target runtests --config ${{ matrix.type.name == 'Debug' && 'Debug' || 'Release' }}
|
||||
|
||||
- name: Test
|
||||
- name: Test (Linux/macOS/MinGW)
|
||||
if: (runner.os != 'Windows' || contains(matrix.platform.name, 'MinGW')) && !contains(matrix.platform.name, 'iOS') && !contains(matrix.platform.name, 'Android')
|
||||
run: |
|
||||
ctest --test-dir build --output-on-failure -C ${{ matrix.type.name == 'Debug' && 'Debug' || 'Release' }} --repeat until-pass:3
|
||||
@ -210,6 +248,47 @@ jobs:
|
||||
gcovr -r $GITHUB_WORKSPACE -x build/coverage.out -s -f 'src/SFML/.*' -f 'include/SFML/.*' ${{ matrix.platform.gcovr_options }} $GITHUB_WORKSPACE
|
||||
fi
|
||||
|
||||
- name: Enable KVM
|
||||
if: contains(matrix.platform.name, 'Android') && matrix.config.emuapi
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: Cache AVD
|
||||
if: contains(matrix.platform.name, 'Android') && matrix.config.emuapi
|
||||
uses: actions/cache@v4
|
||||
id: avd-cache
|
||||
with:
|
||||
path: |
|
||||
~/.android/avd/*
|
||||
~/.android/adb*
|
||||
key: avd-${{ matrix.config.emuarch }}-${{ matrix.config.emuapi }}
|
||||
|
||||
- name: Create AVD and Generate Snapshot for Caching
|
||||
if: contains(matrix.platform.name, 'Android') && matrix.config.emuapi && steps.avd-cache.outputs.cache-hit != 'true'
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: ${{ matrix.config.emuapi }}
|
||||
arch: ${{ matrix.config.emuarch }}
|
||||
force-avd-creation: false
|
||||
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none ${{ matrix.config.emuflags }}
|
||||
disable-animations: false
|
||||
script: echo "Generated AVD snapshot for caching."
|
||||
|
||||
- name: Test (Android)
|
||||
if: contains(matrix.platform.name, 'Android') && matrix.config.emuapi
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: ${{ matrix.config.emuapi }}
|
||||
arch: ${{ matrix.config.emuarch }}
|
||||
force-avd-creation: false
|
||||
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none ${{ matrix.config.emuflags }}
|
||||
disable-animations: true
|
||||
script: |
|
||||
cmake --build build --config ${{ matrix.type.name == 'Debug' && 'Debug' || 'Release' }} --target prepare-android-files
|
||||
ctest --test-dir build --output-on-failure -C ${{ matrix.type.name == 'Debug' && 'Debug' || 'Release' }} --repeat until-pass:3
|
||||
|
||||
- name: Upload Coverage Report to Coveralls
|
||||
if: matrix.type.name == 'Debug' && github.repository == 'SFML/SFML' && !contains(matrix.platform.name, 'iOS') && !contains(matrix.platform.name, 'Android') # Disable upload in forks
|
||||
uses: coverallsapp/github-action@v2
|
||||
@ -311,7 +390,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- { name: Linux, os: ubuntu-22.04, flags: -DSFML_RUN_DISPLAY_TESTS=ON }
|
||||
- { name: Linux, os: ubuntu-22.04, flags: }
|
||||
- { name: Linux DRM, os: ubuntu-22.04, flags: -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_USE_DRM=ON }
|
||||
- { name: Linux GCC OpenGL ES, os: ubuntu-22.04, flags: -DSFML_RUN_DISPLAY_TESTS=OFF -DSFML_OPENGL_ES=ON }
|
||||
|
||||
@ -327,7 +406,7 @@ jobs:
|
||||
|
||||
- name: Install Linux Dependencies
|
||||
if: runner.os == 'Linux'
|
||||
run: sudo apt-get update && sudo apt-get install xorg-dev libxrandr-dev libxcursor-dev libudev-dev libflac-dev libvorbis-dev libgl1-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev xvfb fluxbox
|
||||
run: sudo apt-get update && sudo apt-get install xorg-dev libxrandr-dev libxcursor-dev libudev-dev libflac-dev libvorbis-dev libgl1-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev xvfb fluxbox && sudo apt-get remove -y libasound2
|
||||
|
||||
- name: Configure
|
||||
run: cmake --preset dev -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++ -DSFML_BUILD_EXAMPLES=OFF -DSFML_ENABLE_SANITIZERS=ON ${{matrix.platform.flags}}
|
||||
|
@ -224,9 +224,12 @@ endif()
|
||||
sfml_set_option(SFML_INSTALL_PKGCONFIG_FILES ${SFML_INSTALL_PKGCONFIG_DEFAULT} BOOL "TRUE to automatically install pkg-config files so other projects can find SFML")
|
||||
|
||||
if(SFML_INSTALL_PKGCONFIG_FILES)
|
||||
# account for CMAKE_INSTALL_LIBDIR potentially being an absolute path
|
||||
file(RELATIVE_PATH SFML_RELATIVE_INSTALL_LIBDIR ${CMAKE_INSTALL_PREFIX} ${CMAKE_INSTALL_FULL_LIBDIR})
|
||||
|
||||
# set pkgconfig install directory
|
||||
# this could be e.g. macports on mac or msys2 on windows etc.
|
||||
set(SFML_PKGCONFIG_DIR "/${CMAKE_INSTALL_LIBDIR}/pkgconfig")
|
||||
set(SFML_PKGCONFIG_DIR "/${SFML_RELATIVE_INSTALL_LIBDIR}/pkgconfig")
|
||||
|
||||
if(SFML_OS_FREEBSD OR SFML_OS_OPENBSD OR SFML_OS_NETBSD)
|
||||
set(SFML_PKGCONFIG_DIR "/libdata/pkgconfig")
|
||||
@ -465,6 +468,13 @@ endif()
|
||||
|
||||
sfml_export_targets()
|
||||
|
||||
# configure extras by default when building SFML directly, otherwise hide them
|
||||
sfml_set_option(SFML_CONFIGURE_EXTRAS ${PROJECT_IS_TOP_LEVEL} BOOL "TRUE to configure extras, FALSE to ignore them")
|
||||
|
||||
if(NOT SFML_CONFIGURE_EXTRAS)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(CPACK_PACKAGE_NAME_SUMMARY "Simple and Fast Multimedia Library")
|
||||
set(CPACK_PACKAGE_VENDOR "SFML Team")
|
||||
set(CPACK_PACKAGE_FILE_NAME "SFML-${PROJECT_VERSION}-${CMAKE_CXX_COMPILER_ID}-${CMAKE_CXX_COMPILER_VERSION}-${CMAKE_BUILD_TYPE}")
|
||||
@ -485,13 +495,6 @@ set(CPACK_NSIS_INSTALLER_MUI_ICON_CODE "!define MUI_WELCOMEFINISHPAGE_BITMAP \\\
|
||||
|
||||
include(CPack)
|
||||
|
||||
# configure extras by default when building SFML directly, otherwise hide them
|
||||
sfml_set_option(SFML_CONFIGURE_EXTRAS ${PROJECT_IS_TOP_LEVEL} BOOL "TRUE to configure extras, FALSE to ignore them")
|
||||
|
||||
if(NOT SFML_CONFIGURE_EXTRAS)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# add an option for building the API documentation
|
||||
sfml_set_option(SFML_BUILD_DOC FALSE BOOL "TRUE to generate the API documentation, FALSE to ignore it")
|
||||
if(SFML_BUILD_DOC)
|
||||
|
@ -6,6 +6,8 @@
|
||||
|
||||
- Ensure GNUInstallDirs cache vars are included before first used (#2778, #2779)
|
||||
- [macOS] Fix incorrect variable expansion (#2780)
|
||||
- Issue warning when trying to use UCRT MinGW with precompiled MSVCRT depenencies (#2821)
|
||||
- Fix Nix pkg-config support
|
||||
|
||||
### Audio
|
||||
|
||||
|
@ -368,6 +368,11 @@ function(sfml_add_test target SOURCES DEPENDS)
|
||||
# Delay test registration when cross compiling to avoid running crosscompiled app on host OS
|
||||
if(CMAKE_CROSSCOMPILING)
|
||||
set(CMAKE_CATCH_DISCOVER_TESTS_DISCOVERY_MODE PRE_TEST)
|
||||
|
||||
# When running tests on Android, use a custom shell script to invoke commands using adb shell
|
||||
if(SFML_OS_ANDROID)
|
||||
set_target_properties(${target} PROPERTIES CROSSCOMPILING_EMULATOR "${PROJECT_BINARY_DIR}/run-in-adb-shell.sh")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Add the test
|
||||
|
@ -26,7 +26,12 @@ if(SFML_OS_WINDOWS)
|
||||
PATHS "C:/Program Files/HTML Help Workshop" "C:/Program Files (x86)/HTML Help Workshop"
|
||||
DOC "HTML Help Compiler program")
|
||||
if(DOXYGEN_HHC_PROGRAM)
|
||||
if(DOXYGEN_VERSION VERSION_LESS "1.10.0")
|
||||
set(DOXYGEN_GENERATE_HTMLHELP YES)
|
||||
else()
|
||||
message("Due to conflicts with the HTML output settings in Doxygen ${DOXYGEN_VERSION}, the HTML Help generation will be disabled")
|
||||
set(DOXYGEN_GENERATE_HTMLHELP NO)
|
||||
endif()
|
||||
else()
|
||||
set(DOXYGEN_GENERATE_HTMLHELP NO)
|
||||
endif()
|
||||
|
245
doc/doxyfile.in
245
doc/doxyfile.in
@ -1,4 +1,4 @@
|
||||
# Doxyfile 1.9.6
|
||||
# Doxyfile 1.10.0
|
||||
|
||||
# This file describes the settings to be used by the documentation system
|
||||
# doxygen (www.doxygen.org) for a project.
|
||||
@ -63,6 +63,12 @@ PROJECT_BRIEF =
|
||||
|
||||
PROJECT_LOGO =
|
||||
|
||||
# With the PROJECT_ICON tag one can specify an icon that is included in the tabs
|
||||
# when the HTML document is shown. Doxygen will copy the logo to the output
|
||||
# directory.
|
||||
|
||||
PROJECT_ICON =
|
||||
|
||||
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
|
||||
# into which the generated documentation will be written. If a relative path is
|
||||
# entered, it will be relative to the location where doxygen was started. If
|
||||
@ -380,6 +386,17 @@ MARKDOWN_SUPPORT = YES
|
||||
|
||||
TOC_INCLUDE_HEADINGS = 0
|
||||
|
||||
# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to
|
||||
# generate identifiers for the Markdown headings. Note: Every identifier is
|
||||
# unique.
|
||||
# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a
|
||||
# sequence number starting at 0 and GITHUB use the lower case version of title
|
||||
# with any whitespace replaced by '-' and punctuation characters removed.
|
||||
# The default value is: DOXYGEN.
|
||||
# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
|
||||
|
||||
MARKDOWN_ID_STYLE = DOXYGEN
|
||||
|
||||
# When enabled doxygen tries to link words that correspond to documented
|
||||
# classes, or namespaces to their corresponding documentation. Such a link can
|
||||
# be prevented in individual cases by putting a % sign in front of the word or
|
||||
@ -504,6 +521,14 @@ LOOKUP_CACHE_SIZE = 0
|
||||
|
||||
NUM_PROC_THREADS = 1
|
||||
|
||||
# If the TIMESTAMP tag is set different from NO then each generated page will
|
||||
# contain the date or date and time when the page was generated. Setting this to
|
||||
# NO can help when comparing the output of multiple runs.
|
||||
# Possible values are: YES, NO, DATETIME and DATE.
|
||||
# The default value is: NO.
|
||||
|
||||
TIMESTAMP = NO
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Build related configuration options
|
||||
#---------------------------------------------------------------------------
|
||||
@ -516,7 +541,7 @@ NUM_PROC_THREADS = 1
|
||||
# normally produced when WARNINGS is set to YES.
|
||||
# The default value is: NO.
|
||||
|
||||
EXTRACT_ALL = NO
|
||||
EXTRACT_ALL = YES
|
||||
|
||||
# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
|
||||
# be included in the documentation.
|
||||
@ -889,7 +914,14 @@ WARN_IF_UNDOC_ENUM_VAL = NO
|
||||
# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
|
||||
# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
|
||||
# at the end of the doxygen process doxygen will return with a non-zero status.
|
||||
# Possible values are: NO, YES and FAIL_ON_WARNINGS.
|
||||
# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves
|
||||
# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not
|
||||
# write the warning messages in between other messages but write them at the end
|
||||
# of a run, in case a WARN_LOGFILE is defined the warning messages will be
|
||||
# besides being in the defined file also be shown at the end of a run, unless
|
||||
# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case
|
||||
# the behavior will remain as with the setting FAIL_ON_WARNINGS.
|
||||
# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT.
|
||||
# The default value is: NO.
|
||||
|
||||
WARN_AS_ERROR = NO
|
||||
@ -968,12 +1000,12 @@ INPUT_FILE_ENCODING =
|
||||
# Note the list of default checked file patterns might differ from the list of
|
||||
# default file extension mappings.
|
||||
#
|
||||
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
|
||||
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
|
||||
# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
|
||||
# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
|
||||
# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
|
||||
# *.vhdl, *.ucf, *.qsf and *.ice.
|
||||
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm,
|
||||
# *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl,
|
||||
# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d,
|
||||
# *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to
|
||||
# be provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
|
||||
# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice.
|
||||
|
||||
FILE_PATTERNS = *.hpp
|
||||
|
||||
@ -1020,9 +1052,6 @@ EXCLUDE_PATTERNS = .git \
|
||||
# output. The symbol name can be a fully qualified name, a word, or if the
|
||||
# wildcard * is used, a substring. Examples: ANamespace, AClass,
|
||||
# ANamespace::AClass, ANamespace::*Test
|
||||
#
|
||||
# Note that the wildcards are matched against the file with absolute path, so to
|
||||
# exclude all test directories use the pattern */test/*
|
||||
|
||||
EXCLUDE_SYMBOLS = priv
|
||||
|
||||
@ -1136,7 +1165,8 @@ FORTRAN_COMMENT_AFTER = 72
|
||||
SOURCE_BROWSER = YES
|
||||
|
||||
# Setting the INLINE_SOURCES tag to YES will include the body of functions,
|
||||
# classes and enums directly into the documentation.
|
||||
# multi-line macros, enums or list initialized variables directly into the
|
||||
# documentation.
|
||||
# The default value is: NO.
|
||||
|
||||
INLINE_SOURCES = NO
|
||||
@ -1384,7 +1414,7 @@ HTML_COLORSTYLE = LIGHT
|
||||
# Minimum value: 0, maximum value: 359, default value: 220.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_COLORSTYLE_HUE = 220
|
||||
HTML_COLORSTYLE_HUE = 85
|
||||
|
||||
# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
|
||||
# in the HTML output. For a value of 0 the output will use gray-scales only. A
|
||||
@ -1405,15 +1435,6 @@ HTML_COLORSTYLE_SAT = 100
|
||||
|
||||
HTML_COLORSTYLE_GAMMA = 80
|
||||
|
||||
# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
|
||||
# page will contain the date and time when the page was generated. Setting this
|
||||
# to YES can help to show when doxygen was last run and thus if the
|
||||
# documentation is up to date.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_TIMESTAMP = NO
|
||||
|
||||
# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
|
||||
# documentation will contain a main index with vertical navigation menus that
|
||||
# are dynamically created via JavaScript. If disabled, the navigation index will
|
||||
@ -1433,6 +1454,33 @@ HTML_DYNAMIC_MENUS = NO
|
||||
|
||||
HTML_DYNAMIC_SECTIONS = NO
|
||||
|
||||
# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be
|
||||
# dynamically folded and expanded in the generated HTML source code.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_CODE_FOLDING = YES
|
||||
|
||||
# If the HTML_COPY_CLIPBOARD tag is set to YES then doxygen will show an icon in
|
||||
# the top right corner of code and text fragments that allows the user to copy
|
||||
# its content to the clipboard. Note this only works if supported by the browser
|
||||
# and the web page is served via a secure context (see:
|
||||
# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file:
|
||||
# protocol.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_COPY_CLIPBOARD = YES
|
||||
|
||||
# Doxygen stores a couple of settings persistently in the browser (via e.g.
|
||||
# cookies). By default these settings apply to all HTML pages generated by
|
||||
# doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store
|
||||
# the settings under a project specific key, such that the user preferences will
|
||||
# be stored separately.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_PROJECT_COOKIE =
|
||||
|
||||
# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
|
||||
# shown in the various tree structured indices initially; the user can expand
|
||||
# and collapse entries dynamically later on. Doxygen will expand the tree to
|
||||
@ -1563,6 +1611,16 @@ BINARY_TOC = NO
|
||||
|
||||
TOC_EXPAND = NO
|
||||
|
||||
# The SITEMAP_URL tag is used to specify the full URL of the place where the
|
||||
# generated documentation will be placed on the server by the user during the
|
||||
# deployment of the documentation. The generated sitemap is called sitemap.xml
|
||||
# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL
|
||||
# is specified no sitemap is generated. For information about the sitemap
|
||||
# protocol see https://www.sitemaps.org
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
SITEMAP_URL =
|
||||
|
||||
# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
|
||||
# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
|
||||
# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
|
||||
@ -2051,9 +2109,16 @@ PDF_HYPERLINKS = YES
|
||||
|
||||
USE_PDFLATEX = YES
|
||||
|
||||
# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
|
||||
# command to the generated LaTeX files. This will instruct LaTeX to keep running
|
||||
# if errors occur, instead of asking the user for help.
|
||||
# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error.
|
||||
# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch
|
||||
# mode nothing is printed on the terminal, errors are scrolled as if <return> is
|
||||
# hit at every error; missing files that TeX tries to input or request from
|
||||
# keyboard input (\read on a not open input stream) cause the job to abort,
|
||||
# NON_STOP In nonstop mode the diagnostic message will appear on the terminal,
|
||||
# but there is no possibility of user interaction just like in batch mode,
|
||||
# SCROLL In scroll mode, TeX will stop only for missing files to input or if
|
||||
# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at
|
||||
# each error, asking for user intervention.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
@ -2074,14 +2139,6 @@ LATEX_HIDE_INDICES = NO
|
||||
|
||||
LATEX_BIB_STYLE = plain
|
||||
|
||||
# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
|
||||
# page will contain the date and time when the page was generated. Setting this
|
||||
# to NO can help when comparing the output of multiple runs.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
LATEX_TIMESTAMP = NO
|
||||
|
||||
# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
|
||||
# path from which the emoji images will be read. If a relative path is entered,
|
||||
# it will be relative to the LATEX_OUTPUT directory. If left blank the
|
||||
@ -2247,7 +2304,7 @@ DOCBOOK_OUTPUT = docbook
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
|
||||
# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
|
||||
# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures
|
||||
# the structure of the code including all documentation. Note that this feature
|
||||
# is still experimental and incomplete at the moment.
|
||||
# The default value is: NO.
|
||||
@ -2258,6 +2315,28 @@ GENERATE_AUTOGEN_DEF = NO
|
||||
# Configuration options related to Sqlite3 output
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3
|
||||
# database with symbols found by doxygen stored in tables.
|
||||
# The default value is: NO.
|
||||
|
||||
GENERATE_SQLITE3 = NO
|
||||
|
||||
# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be
|
||||
# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put
|
||||
# in front of it.
|
||||
# The default directory is: sqlite3.
|
||||
# This tag requires that the tag GENERATE_SQLITE3 is set to YES.
|
||||
|
||||
SQLITE3_OUTPUT = sqlite3
|
||||
|
||||
# The SQLITE3_RECREATE_DB tag is set to YES, the existing doxygen_sqlite3.db
|
||||
# database file will be recreated with each doxygen run. If set to NO, doxygen
|
||||
# will warn if a database file is already found and not modify it.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag GENERATE_SQLITE3 is set to YES.
|
||||
|
||||
SQLITE3_RECREATE_DB = YES
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the Perl module output
|
||||
#---------------------------------------------------------------------------
|
||||
@ -2405,15 +2484,15 @@ TAGFILES =
|
||||
|
||||
GENERATE_TAGFILE = "@DOXYGEN_OUTPUT_DIR@/SFML.tag"
|
||||
|
||||
# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
|
||||
# the class index. If set to NO, only the inherited external classes will be
|
||||
# listed.
|
||||
# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces
|
||||
# will be listed in the class and namespace index. If set to NO, only the
|
||||
# inherited external classes will be listed.
|
||||
# The default value is: NO.
|
||||
|
||||
ALLEXTERNALS = NO
|
||||
|
||||
# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
|
||||
# in the modules index. If set to NO, only the current project's groups will be
|
||||
# in the topic index. If set to NO, only the current project's groups will be
|
||||
# listed.
|
||||
# The default value is: YES.
|
||||
|
||||
@ -2427,16 +2506,9 @@ EXTERNAL_GROUPS = YES
|
||||
EXTERNAL_PAGES = YES
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the dot tool
|
||||
# Configuration options related to diagram generator tools
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# You can include diagrams made with dia in doxygen documentation. Doxygen will
|
||||
# then run dia to produce the diagram and insert it in the documentation. The
|
||||
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
|
||||
# If left empty dia is assumed to be found in the default search path.
|
||||
|
||||
DIA_PATH =
|
||||
|
||||
# If set to YES the inheritance and collaboration graphs will hide inheritance
|
||||
# and usage relations if the target is undocumented or is not a class.
|
||||
# The default value is: YES.
|
||||
@ -2445,7 +2517,7 @@ HIDE_UNDOC_RELATIONS = YES
|
||||
|
||||
# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
|
||||
# available from the path. This tool is part of Graphviz (see:
|
||||
# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
|
||||
# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
|
||||
# Bell Labs. The other options in this section have no effect if this option is
|
||||
# set to NO
|
||||
# The default value is: NO.
|
||||
@ -2498,13 +2570,19 @@ DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4"
|
||||
|
||||
DOT_FONTPATH =
|
||||
|
||||
# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a
|
||||
# graph for each documented class showing the direct and indirect inheritance
|
||||
# relations. In case HAVE_DOT is set as well dot will be used to draw the graph,
|
||||
# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set
|
||||
# to TEXT the direct and indirect inheritance relations will be shown as texts /
|
||||
# links.
|
||||
# Possible values are: NO, YES, TEXT and GRAPH.
|
||||
# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will
|
||||
# generate a graph for each documented class showing the direct and indirect
|
||||
# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and
|
||||
# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case
|
||||
# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the
|
||||
# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used.
|
||||
# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance
|
||||
# relations will be shown as texts / links. Explicit enabling an inheritance
|
||||
# graph or choosing a different representation for an inheritance graph of a
|
||||
# specific class, can be accomplished by means of the command \inheritancegraph.
|
||||
# Disabling an inheritance graph can be accomplished by means of the command
|
||||
# \hideinheritancegraph.
|
||||
# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN.
|
||||
# The default value is: YES.
|
||||
|
||||
CLASS_GRAPH = YES
|
||||
@ -2512,15 +2590,21 @@ CLASS_GRAPH = YES
|
||||
# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
|
||||
# graph for each documented class showing the direct and indirect implementation
|
||||
# dependencies (inheritance, containment, and class references variables) of the
|
||||
# class with other documented classes.
|
||||
# class with other documented classes. Explicit enabling a collaboration graph,
|
||||
# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the
|
||||
# command \collaborationgraph. Disabling a collaboration graph can be
|
||||
# accomplished by means of the command \hidecollaborationgraph.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
COLLABORATION_GRAPH = YES
|
||||
|
||||
# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
|
||||
# groups, showing the direct groups dependencies. See also the chapter Grouping
|
||||
# in the manual.
|
||||
# groups, showing the direct groups dependencies. Explicit enabling a group
|
||||
# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means
|
||||
# of the command \groupgraph. Disabling a directory graph can be accomplished by
|
||||
# means of the command \hidegroupgraph. See also the chapter Grouping in the
|
||||
# manual.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
@ -2562,8 +2646,8 @@ DOT_UML_DETAILS = NO
|
||||
|
||||
# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters
|
||||
# to display on a single line. If the actual line length exceeds this threshold
|
||||
# significantly it will wrapped across multiple lines. Some heuristics are apply
|
||||
# to avoid ugly line breaks.
|
||||
# significantly it will be wrapped across multiple lines. Some heuristics are
|
||||
# applied to avoid ugly line breaks.
|
||||
# Minimum value: 0, maximum value: 1000, default value: 17.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
@ -2580,7 +2664,9 @@ TEMPLATE_RELATIONS = NO
|
||||
# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
|
||||
# YES then doxygen will generate a graph for each documented file showing the
|
||||
# direct and indirect include dependencies of the file with other documented
|
||||
# files.
|
||||
# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO,
|
||||
# can be accomplished by means of the command \includegraph. Disabling an
|
||||
# include graph can be accomplished by means of the command \hideincludegraph.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
@ -2589,7 +2675,10 @@ INCLUDE_GRAPH = YES
|
||||
# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
|
||||
# set to YES then doxygen will generate a graph for each documented file showing
|
||||
# the direct and indirect include dependencies of the file with other documented
|
||||
# files.
|
||||
# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set
|
||||
# to NO, can be accomplished by means of the command \includedbygraph. Disabling
|
||||
# an included by graph can be accomplished by means of the command
|
||||
# \hideincludedbygraph.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
@ -2629,7 +2718,10 @@ GRAPHICAL_HIERARCHY = YES
|
||||
# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
|
||||
# dependencies a directory has on other directories in a graphical way. The
|
||||
# dependency relations are determined by the #include relations between the
|
||||
# files in the directories.
|
||||
# files in the directories. Explicit enabling a directory graph, when
|
||||
# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command
|
||||
# \directorygraph. Disabling a directory graph can be accomplished by means of
|
||||
# the command \hidedirectorygraph.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
@ -2645,7 +2737,7 @@ DIR_GRAPH_MAX_DEPTH = 1
|
||||
# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
|
||||
# generated by dot. For an explanation of the image formats see the section
|
||||
# output formats in the documentation of the dot tool (Graphviz (see:
|
||||
# http://www.graphviz.org/)).
|
||||
# https://www.graphviz.org/)).
|
||||
# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
|
||||
# to make the SVG files visible in IE 9+ (other browsers do not have this
|
||||
# requirement).
|
||||
@ -2682,11 +2774,12 @@ DOT_PATH =
|
||||
|
||||
DOTFILE_DIRS =
|
||||
|
||||
# The MSCFILE_DIRS tag can be used to specify one or more directories that
|
||||
# contain msc files that are included in the documentation (see the \mscfile
|
||||
# command).
|
||||
# You can include diagrams made with dia in doxygen documentation. Doxygen will
|
||||
# then run dia to produce the diagram and insert it in the documentation. The
|
||||
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
|
||||
# If left empty dia is assumed to be found in the default search path.
|
||||
|
||||
MSCFILE_DIRS =
|
||||
DIA_PATH =
|
||||
|
||||
# The DIAFILE_DIRS tag can be used to specify one or more directories that
|
||||
# contain dia files that are included in the documentation (see the \diafile
|
||||
@ -2763,3 +2856,19 @@ GENERATE_LEGEND = YES
|
||||
# The default value is: YES.
|
||||
|
||||
DOT_CLEANUP = YES
|
||||
|
||||
# You can define message sequence charts within doxygen comments using the \msc
|
||||
# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will
|
||||
# use a built-in version of mscgen tool to produce the charts. Alternatively,
|
||||
# the MSCGEN_TOOL tag can also specify the name an external tool. For instance,
|
||||
# specifying prog as the value, doxygen will call the tool as prog -T
|
||||
# <outfile_format> -o <outputfile> <inputfile>. The external tool should support
|
||||
# output file formats "png", "eps", "svg", and "ismap".
|
||||
|
||||
MSCGEN_TOOL =
|
||||
|
||||
# The MSCFILE_DIRS tag can be used to specify one or more directories that
|
||||
# contain msc files that are included in the documentation (see the \mscfile
|
||||
# command).
|
||||
|
||||
MSCFILE_DIRS =
|
||||
|
@ -147,12 +147,53 @@ div.fragment {
|
||||
|
||||
font-family: Consolas, "Liberation Mono", Courier, monospace;
|
||||
font-size: 10pt;
|
||||
position: relative;
|
||||
padding: 0.5em 1em;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #bbb;
|
||||
border-radius(5px);
|
||||
}
|
||||
|
||||
.clipboard {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
display: inline;
|
||||
overflow: auto;
|
||||
fill: black;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clipboard.success {
|
||||
border: 1px solid black;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.fragment:hover .clipboard, .clipboard.success {
|
||||
opacity: .28;
|
||||
}
|
||||
|
||||
.clipboard:hover, .clipboard.success {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.clipboard:active:not([class~=success]) svg {
|
||||
transform: scale(.91);
|
||||
}
|
||||
|
||||
.clipboard.success svg {
|
||||
fill: #2EC82E;
|
||||
}
|
||||
|
||||
.clipboard.success {
|
||||
border-color: #2EC82E;
|
||||
}
|
||||
|
||||
div.line {
|
||||
min-height: 13px;
|
||||
text-wrap: unrestricted;
|
||||
@ -1254,7 +1295,7 @@ div.contents ul li {
|
||||
width: 24px;
|
||||
height: 18px;
|
||||
margin-bottom: 4px;
|
||||
background-image:url('folderopen.png');
|
||||
background-image:url('folderopen.svg');
|
||||
background-position: 0px -4px;
|
||||
background-repeat: repeat-y;
|
||||
vertical-align:top;
|
||||
@ -1265,7 +1306,7 @@ div.contents ul li {
|
||||
width: 24px;
|
||||
height: 18px;
|
||||
margin-bottom: 4px;
|
||||
background-image:url('folderclosed.png');
|
||||
background-image:url('folderclosed.svg');
|
||||
background-position: 0px -4px;
|
||||
background-repeat: repeat-y;
|
||||
vertical-align:top;
|
||||
@ -1276,7 +1317,7 @@ div.contents ul li {
|
||||
width: 24px;
|
||||
height: 18px;
|
||||
margin-bottom: 4px;
|
||||
background-image:url('doc.png');
|
||||
background-image:url('doc.svg');
|
||||
background-position: 0px -4px;
|
||||
background-repeat: repeat-y;
|
||||
vertical-align:top;
|
||||
|
@ -1,7 +1,7 @@
|
||||
</div>
|
||||
<div id="footer-container">
|
||||
<div id="footer">
|
||||
SFML is licensed under the terms and conditions of the <a href="https://www.sfml-dev.org/license.php">zlib/png license</a>.<br>
|
||||
SFML is licensed under the terms and conditions of the <a href="https://www.sfml-dev.org/license.php">zlib/png license</a>.<br />
|
||||
Copyright © Laurent Gomila ::
|
||||
Documentation generated by <a href="http://www.doxygen.org/" title="doxygen website">doxygen</a> ::
|
||||
</div>
|
||||
|
@ -2,11 +2,12 @@
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>SFML - Simple and Fast Multimedia Library</title>
|
||||
<meta http-equiv="Content-Type" content="text/html;"/>
|
||||
<meta charset="utf-8"/>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
|
||||
<link rel="stylesheet" type="text/css" href="doxygen.css" title="default" media="screen,print" />
|
||||
<script type="text/javascript" src="jquery.js"></script>
|
||||
<script type="text/javascript" src="dynsections.js"></script>
|
||||
<script type="text/javascript" src="clipboard.js"></script>
|
||||
<script type="text/javascript" src="cookie.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="search/search.css" />
|
||||
<link rel="stylesheet" type="text/css" href="searchOverrides.css" />
|
||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
||||
@ -24,3 +25,4 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="content">
|
||||
<div>
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <SFML/Audio.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@ -18,6 +19,7 @@ namespace
|
||||
constexpr auto windowWidth = 800u;
|
||||
constexpr auto windowHeight = 600u;
|
||||
constexpr auto pi = 3.14159265359f;
|
||||
constexpr auto sqrt2 = 2.0f * 0.707106781186547524401f;
|
||||
|
||||
std::filesystem::path resourcesDir()
|
||||
{
|
||||
@ -85,7 +87,7 @@ protected:
|
||||
private:
|
||||
// Virtual functions to be implemented in derived effects
|
||||
virtual void onUpdate(float time, float x, float y) = 0;
|
||||
virtual void onDraw(sf::RenderTarget& target, const sf::RenderStates& states) const = 0;
|
||||
virtual void onDraw(sf::RenderTarget& target, sf::RenderStates states) const = 0;
|
||||
virtual void onStart() = 0;
|
||||
virtual void onStop() = 0;
|
||||
|
||||
@ -113,7 +115,7 @@ public:
|
||||
|
||||
// Load the music file
|
||||
if (!m_music.openFromFile(resourcesDir() / "doodle_pop.ogg"))
|
||||
sf::err() << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl;
|
||||
std::cerr << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl;
|
||||
|
||||
// Set the music to loop
|
||||
m_music.setLoop(true);
|
||||
@ -128,7 +130,7 @@ public:
|
||||
m_music.setPosition({m_position.x, m_position.y, 0.f});
|
||||
}
|
||||
|
||||
void onDraw(sf::RenderTarget& target, const sf::RenderStates& states) const override
|
||||
void onDraw(sf::RenderTarget& target, sf::RenderStates states) const override
|
||||
{
|
||||
auto statesCopy(states);
|
||||
statesCopy.transform = sf::Transform::Identity;
|
||||
@ -172,7 +174,7 @@ public:
|
||||
{
|
||||
// Load the music file
|
||||
if (!m_music.openFromFile(resourcesDir() / "doodle_pop.ogg"))
|
||||
sf::err() << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl;
|
||||
std::cerr << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl;
|
||||
|
||||
// Set the music to loop
|
||||
m_music.setLoop(true);
|
||||
@ -202,7 +204,7 @@ public:
|
||||
m_volumeText.setString("Volume: " + std::to_string(m_volume));
|
||||
}
|
||||
|
||||
void onDraw(sf::RenderTarget& target, const sf::RenderStates& states) const override
|
||||
void onDraw(sf::RenderTarget& target, sf::RenderStates states) const override
|
||||
{
|
||||
target.draw(m_pitchText, states);
|
||||
target.draw(m_volumeText, states);
|
||||
@ -275,7 +277,7 @@ public:
|
||||
|
||||
// Load the music file
|
||||
if (!m_music.openFromFile(resourcesDir() / "doodle_pop.ogg"))
|
||||
sf::err() << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl;
|
||||
std::cerr << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl;
|
||||
|
||||
// Set the music to loop
|
||||
m_music.setLoop(true);
|
||||
@ -305,7 +307,7 @@ public:
|
||||
m_music.setPosition({m_position.x, m_position.y, 0.f});
|
||||
}
|
||||
|
||||
void onDraw(sf::RenderTarget& target, const sf::RenderStates& states) const override
|
||||
void onDraw(sf::RenderTarget& target, sf::RenderStates states) const override
|
||||
{
|
||||
auto statesCopy(states);
|
||||
|
||||
@ -375,7 +377,7 @@ public:
|
||||
m_currentFrequency.setString("Frequency: " + std::to_string(m_frequency) + " Hz");
|
||||
}
|
||||
|
||||
void onDraw(sf::RenderTarget& target, const sf::RenderStates& states) const override
|
||||
void onDraw(sf::RenderTarget& target, sf::RenderStates states) const override
|
||||
{
|
||||
target.draw(m_instruction, states);
|
||||
target.draw(m_currentType, states);
|
||||
@ -547,7 +549,7 @@ public:
|
||||
setDopplerFactor(m_factor);
|
||||
}
|
||||
|
||||
void onDraw(sf::RenderTarget& target, const sf::RenderStates& states) const override
|
||||
void onDraw(sf::RenderTarget& target, sf::RenderStates states) const override
|
||||
{
|
||||
auto statesCopy(states);
|
||||
statesCopy.transform = sf::Transform::Identity;
|
||||
@ -615,6 +617,425 @@ private:
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Processing base class
|
||||
////////////////////////////////////////////////////////////
|
||||
class Processing : public Effect
|
||||
{
|
||||
public:
|
||||
void onUpdate([[maybe_unused]] float time, float x, float y) override
|
||||
{
|
||||
m_position = {windowWidth * x - 10.f, windowHeight * y - 10.f};
|
||||
m_music.setPosition({m_position.x, m_position.y, 0.f});
|
||||
}
|
||||
|
||||
void onDraw(sf::RenderTarget& target, sf::RenderStates states) const override
|
||||
{
|
||||
auto statesCopy(states);
|
||||
statesCopy.transform = sf::Transform::Identity;
|
||||
statesCopy.transform.translate(m_position);
|
||||
|
||||
target.draw(m_listener, states);
|
||||
target.draw(m_soundShape, statesCopy);
|
||||
target.draw(m_enabledText);
|
||||
target.draw(m_instructions);
|
||||
}
|
||||
|
||||
void onStart() override
|
||||
{
|
||||
// Synchronize listener audio position with graphical position
|
||||
sf::Listener::setPosition({m_listener.getPosition().x, m_listener.getPosition().y, 0.f});
|
||||
|
||||
m_music.play();
|
||||
}
|
||||
|
||||
void onStop() override
|
||||
{
|
||||
m_music.stop();
|
||||
}
|
||||
|
||||
protected:
|
||||
Processing(std::string name) :
|
||||
Effect(std::move(name)),
|
||||
m_enabledText(getFont(), "Processing: Enabled"),
|
||||
m_instructions(getFont(), "Press Space to enable/disable processing")
|
||||
{
|
||||
m_listener.setPosition({(windowWidth - 20.f) / 2.f, (windowHeight - 20.f) / 2.f});
|
||||
m_listener.setFillColor(sf::Color::Red);
|
||||
|
||||
m_enabledText.setPosition({windowWidth / 2.f - 120.f, windowHeight * 3.f / 4.f - 50.f});
|
||||
m_instructions.setPosition({windowWidth / 2.f - 250.f, windowHeight * 3.f / 4.f});
|
||||
|
||||
// Load the music file
|
||||
if (!m_music.openFromFile(resourcesDir() / "doodle_pop.ogg"))
|
||||
std::cerr << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl;
|
||||
|
||||
// Set the music to loop
|
||||
m_music.setLoop(true);
|
||||
|
||||
// Set attenuation to a nice value
|
||||
m_music.setAttenuation(0.0f);
|
||||
}
|
||||
|
||||
sf::Music& getMusic()
|
||||
{
|
||||
return m_music;
|
||||
}
|
||||
|
||||
const std::shared_ptr<bool>& getEnabled() const
|
||||
{
|
||||
return m_enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
void onKey(sf::Keyboard::Key key) override
|
||||
{
|
||||
if (key == sf::Keyboard::Key::Space)
|
||||
*m_enabled = !*m_enabled;
|
||||
|
||||
m_enabledText.setString(*m_enabled ? "Processing: Enabled" : "Processing: Disabled");
|
||||
}
|
||||
|
||||
sf::CircleShape m_listener{20.f};
|
||||
sf::CircleShape m_soundShape{20.f};
|
||||
sf::Vector2f m_position;
|
||||
sf::Music m_music;
|
||||
std::shared_ptr<bool> m_enabled{std::make_shared<bool>(true)};
|
||||
sf::Text m_enabledText;
|
||||
sf::Text m_instructions;
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Biquad Filter (https://github.com/dimtass/DSP-Cpp-filters)
|
||||
////////////////////////////////////////////////////////////
|
||||
class BiquadFilter : public Processing
|
||||
{
|
||||
protected:
|
||||
struct Coefficients
|
||||
{
|
||||
float a0{};
|
||||
float a1{};
|
||||
float a2{};
|
||||
float b1{};
|
||||
float b2{};
|
||||
float c0{};
|
||||
float d0{};
|
||||
};
|
||||
|
||||
using Processing::Processing;
|
||||
|
||||
void setCoefficients(const Coefficients& coefficients)
|
||||
{
|
||||
auto& music = getMusic();
|
||||
|
||||
struct State
|
||||
{
|
||||
float xnz1{};
|
||||
float xnz2{};
|
||||
float ynz1{};
|
||||
float ynz2{};
|
||||
};
|
||||
|
||||
// We use a mutable lambda to tie the lifetime of the state and coefficients to the lambda itself
|
||||
// This is necessary since the Echo object will be destroyed before the Music object
|
||||
// While the Music object exists, it is possible that the audio engine will try to call
|
||||
// this lambda hence we need to always have usable coefficients and state until the Music and the
|
||||
// associated lambda are destroyed
|
||||
music.setEffectProcessor(
|
||||
[coefficients,
|
||||
enabled = getEnabled(),
|
||||
state = std::vector<State>(music.getChannelCount())](const float* inputFrames,
|
||||
unsigned int& inputFrameCount,
|
||||
float* outputFrames,
|
||||
unsigned int& outputFrameCount,
|
||||
unsigned int frameChannelCount) mutable
|
||||
{
|
||||
for (auto frame = 0u; frame < outputFrameCount; ++frame)
|
||||
{
|
||||
for (auto channel = 0u; channel < frameChannelCount; ++channel)
|
||||
{
|
||||
auto& channelState = state[channel];
|
||||
|
||||
const auto xn = inputFrames ? inputFrames[channel] : 0.f; // Read silence if no input data available
|
||||
const auto yn = coefficients.a0 * xn + coefficients.a1 * channelState.xnz1 +
|
||||
coefficients.a2 * channelState.xnz2 - coefficients.b1 * channelState.ynz1 -
|
||||
coefficients.b2 * channelState.ynz2;
|
||||
|
||||
channelState.xnz2 = channelState.xnz1;
|
||||
channelState.xnz1 = xn;
|
||||
channelState.ynz2 = channelState.ynz1;
|
||||
channelState.ynz1 = yn;
|
||||
|
||||
outputFrames[channel] = *enabled ? yn : xn;
|
||||
}
|
||||
|
||||
inputFrames += (inputFrames ? frameChannelCount : 0u);
|
||||
outputFrames += frameChannelCount;
|
||||
}
|
||||
|
||||
// We processed data 1:1
|
||||
inputFrameCount = outputFrameCount;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// High-pass Filter (https://github.com/dimtass/DSP-Cpp-filters)
|
||||
////////////////////////////////////////////////////////////
|
||||
struct HighPassFilter : BiquadFilter
|
||||
{
|
||||
HighPassFilter() : BiquadFilter("High-pass Filter")
|
||||
{
|
||||
static constexpr auto cutoffFrequency = 2000.f;
|
||||
|
||||
const auto c = std::tan(pi * cutoffFrequency / static_cast<float>(getMusic().getSampleRate()));
|
||||
|
||||
Coefficients coefficients;
|
||||
|
||||
coefficients.a0 = 1.f / (1.f + sqrt2 * c + std::pow(c, 2.f));
|
||||
coefficients.a1 = -2.f * coefficients.a0;
|
||||
coefficients.a2 = coefficients.a0;
|
||||
coefficients.b1 = 2.f * coefficients.a0 * (std::pow(c, 2.f) - 1.f);
|
||||
coefficients.b2 = coefficients.a0 * (1.f - sqrt2 * c + std::pow(c, 2.f));
|
||||
|
||||
setCoefficients(coefficients);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Low-pass Filter (https://github.com/dimtass/DSP-Cpp-filters)
|
||||
////////////////////////////////////////////////////////////
|
||||
struct LowPassFilter : BiquadFilter
|
||||
{
|
||||
LowPassFilter() : BiquadFilter("Low-pass Filter")
|
||||
{
|
||||
static constexpr auto cutoffFrequency = 500.f;
|
||||
|
||||
const auto c = 1.f / std::tan(pi * cutoffFrequency / static_cast<float>(getMusic().getSampleRate()));
|
||||
|
||||
Coefficients coefficients;
|
||||
|
||||
coefficients.a0 = 1.f / (1.f + sqrt2 * c + std::pow(c, 2.f));
|
||||
coefficients.a1 = 2.f * coefficients.a0;
|
||||
coefficients.a2 = coefficients.a0;
|
||||
coefficients.b1 = 2.f * coefficients.a0 * (1.f - std::pow(c, 2.f));
|
||||
coefficients.b2 = coefficients.a0 * (1.f - sqrt2 * c + std::pow(c, 2.f));
|
||||
|
||||
setCoefficients(coefficients);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Echo (miniaudio implementation)
|
||||
////////////////////////////////////////////////////////////
|
||||
struct Echo : Processing
|
||||
{
|
||||
Echo() : Processing("Echo")
|
||||
{
|
||||
auto& music = getMusic();
|
||||
|
||||
static constexpr auto delay = 0.2f;
|
||||
static constexpr auto decay = 0.75f;
|
||||
static constexpr auto wet = 0.8f;
|
||||
static constexpr auto dry = 1.f;
|
||||
|
||||
const auto channelCount = music.getChannelCount();
|
||||
const auto sampleRate = music.getSampleRate();
|
||||
const auto delayInFrames = static_cast<unsigned int>(static_cast<float>(sampleRate) * delay);
|
||||
|
||||
// We use a mutable lambda to tie the lifetime of the state to the lambda itself
|
||||
// This is necessary since the Echo object will be destroyed before the Music object
|
||||
// While the Music object exists, it is possible that the audio engine will try to call
|
||||
// this lambda hence we need to always have a usable state until the Music and the
|
||||
// associated lambda are destroyed
|
||||
music.setEffectProcessor(
|
||||
[delayInFrames,
|
||||
enabled = getEnabled(),
|
||||
buffer = std::vector<float>(delayInFrames * channelCount, 0.f),
|
||||
cursor = 0u](const float* inputFrames,
|
||||
unsigned int& inputFrameCount,
|
||||
float* outputFrames,
|
||||
unsigned int& outputFrameCount,
|
||||
unsigned int frameChannelCount) mutable
|
||||
{
|
||||
for (auto frame = 0u; frame < outputFrameCount; ++frame)
|
||||
{
|
||||
for (auto channel = 0u; channel < frameChannelCount; ++channel)
|
||||
{
|
||||
const auto input = inputFrames ? inputFrames[channel] : 0.f; // Read silence if no input data available
|
||||
const auto bufferIndex = (cursor * frameChannelCount) + channel;
|
||||
buffer[bufferIndex] = (buffer[bufferIndex] * decay) + (input * dry);
|
||||
outputFrames[channel] = *enabled ? buffer[bufferIndex] * wet : input;
|
||||
}
|
||||
|
||||
cursor = (cursor + 1) % delayInFrames;
|
||||
|
||||
inputFrames += (inputFrames ? frameChannelCount : 0u);
|
||||
outputFrames += frameChannelCount;
|
||||
}
|
||||
|
||||
// We processed data 1:1
|
||||
inputFrameCount = outputFrameCount;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Reverb (https://github.com/sellicott/DSP-FFMpeg-Reverb)
|
||||
////////////////////////////////////////////////////////////
|
||||
class Reverb : public Processing
|
||||
{
|
||||
public:
|
||||
Reverb() : Processing("Reverb")
|
||||
{
|
||||
auto& music = getMusic();
|
||||
|
||||
static constexpr auto sustain = 0.7f; // [0.f; 1.f]
|
||||
|
||||
const auto channelCount = music.getChannelCount();
|
||||
const auto sampleRate = music.getSampleRate();
|
||||
|
||||
std::vector<ReverbFilter<float>> filters;
|
||||
filters.reserve(channelCount);
|
||||
|
||||
for (auto i = 0u; i < channelCount; ++i)
|
||||
filters.emplace_back(sampleRate, sustain);
|
||||
|
||||
// We use a mutable lambda to tie the lifetime of the state to the lambda itself
|
||||
// This is necessary since the Echo object will be destroyed before the Music object
|
||||
// While the Music object exists, it is possible that the audio engine will try to call
|
||||
// this lambda hence we need to always have a usable state until the Music and the
|
||||
// associated lambda are destroyed
|
||||
music.setEffectProcessor(
|
||||
[filters, enabled = getEnabled()](const float* inputFrames,
|
||||
unsigned int& inputFrameCount,
|
||||
float* outputFrames,
|
||||
unsigned int& outputFrameCount,
|
||||
unsigned int frameChannelCount) mutable
|
||||
{
|
||||
for (auto frame = 0u; frame < outputFrameCount; ++frame)
|
||||
{
|
||||
for (auto channel = 0u; channel < frameChannelCount; ++channel)
|
||||
{
|
||||
const auto input = inputFrames ? inputFrames[channel] : 0.f; // Read silence if no input data available
|
||||
outputFrames[channel] = *enabled ? filters[channel](input) : input;
|
||||
}
|
||||
|
||||
inputFrames += (inputFrames ? frameChannelCount : 0u);
|
||||
outputFrames += frameChannelCount;
|
||||
}
|
||||
|
||||
// We processed data 1:1
|
||||
inputFrameCount = outputFrameCount;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
class AllPassFilter
|
||||
{
|
||||
public:
|
||||
AllPassFilter(std::size_t delay, float theGain) : m_buffer(delay, {}), m_gain(theGain)
|
||||
{
|
||||
}
|
||||
|
||||
T operator()(T input)
|
||||
{
|
||||
const auto output = m_buffer[m_cursor];
|
||||
input = static_cast<T>(input + m_gain * output);
|
||||
m_buffer[m_cursor] = input;
|
||||
m_cursor = (m_cursor + 1) % m_buffer.size();
|
||||
return static_cast<T>(-m_gain * input + output);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<T> m_buffer;
|
||||
std::size_t m_cursor{};
|
||||
const float m_gain{};
|
||||
};
|
||||
|
||||
|
||||
template <typename T>
|
||||
class FIRFilter
|
||||
{
|
||||
public:
|
||||
FIRFilter(std::vector<float> taps) : m_taps(std::move(taps))
|
||||
{
|
||||
}
|
||||
|
||||
T operator()(T input)
|
||||
{
|
||||
m_buffer[m_cursor] = input;
|
||||
m_cursor = (m_cursor + 1) % m_buffer.size();
|
||||
|
||||
T output{};
|
||||
|
||||
for (auto i = 0u; i < m_taps.size(); ++i)
|
||||
output += static_cast<T>(m_taps[i] * m_buffer[(m_cursor + i) % m_buffer.size()]);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<float> m_taps;
|
||||
std::vector<T> m_buffer = std::vector<T>(m_taps.size(), {});
|
||||
std::size_t m_cursor{};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class ReverbFilter
|
||||
{
|
||||
public:
|
||||
ReverbFilter(unsigned int sampleRate, float feedbackGain) :
|
||||
m_allPass{{sampleRate / 10, 0.6f}, {sampleRate / 30, -0.6f}, {sampleRate / 90, 0.6f}, {sampleRate / 270, -0.6f}},
|
||||
m_fir({0.003369f, 0.002810f, 0.001758f, 0.000340f, -0.001255f, -0.002793f, -0.004014f, -0.004659f,
|
||||
-0.004516f, -0.003464f, -0.001514f, 0.001148f, 0.004157f, 0.006986f, 0.009003f, 0.009571f,
|
||||
0.008173f, 0.004560f, -0.001120f, -0.008222f, -0.015581f, -0.021579f, -0.024323f, -0.021933f,
|
||||
-0.012904f, 0.003500f, 0.026890f, 0.055537f, 0.086377f, 0.115331f, 0.137960f, 0.150407f,
|
||||
0.150407f, 0.137960f, 0.115331f, 0.086377f, 0.055537f, 0.026890f, 0.003500f, -0.012904f,
|
||||
-0.021933f, -0.024323f, -0.021579f, -0.015581f, -0.008222f, -0.001120f, 0.004560f, 0.008173f,
|
||||
0.009571f, 0.009003f, 0.006986f, 0.004157f, 0.001148f, -0.001514f, -0.003464f, -0.004516f,
|
||||
-0.004659f, -0.004014f, -0.002793f, -0.001255f, 0.000340f, 0.001758f, 0.002810f, 0.003369f}),
|
||||
m_buffer(sampleRate / 5, {}), // sample rate / 5 = 200ms buffer size
|
||||
m_feedbackGain(feedbackGain)
|
||||
{
|
||||
}
|
||||
|
||||
T operator()(T input)
|
||||
{
|
||||
auto output = static_cast<T>(0.7f * input + m_feedbackGain * m_buffer[m_cursor]);
|
||||
|
||||
for (auto& f : m_allPass)
|
||||
output = f(output);
|
||||
|
||||
output = m_fir(output);
|
||||
|
||||
m_buffer[m_cursor] = output;
|
||||
m_cursor = (m_cursor + 1) % m_buffer.size();
|
||||
|
||||
output += 0.5f * m_buffer[(m_cursor + 1 * m_interval - 1) % m_buffer.size()];
|
||||
output += 0.25f * m_buffer[(m_cursor + 2 * m_interval - 1) % m_buffer.size()];
|
||||
output += 0.125f * m_buffer[(m_cursor + 3 * m_interval - 1) % m_buffer.size()];
|
||||
|
||||
return 0.6f * output + input;
|
||||
}
|
||||
|
||||
private:
|
||||
AllPassFilter<T> m_allPass[4];
|
||||
FIRFilter<T> m_fir;
|
||||
std::vector<T> m_buffer;
|
||||
std::size_t m_cursor{};
|
||||
const std::size_t m_interval{m_buffer.size() / 3};
|
||||
const float m_feedbackGain{};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// Entry point of application
|
||||
///
|
||||
@ -641,14 +1062,20 @@ int main()
|
||||
Attenuation attenuationEffect;
|
||||
Tone toneEffect;
|
||||
Doppler dopplerEffect;
|
||||
HighPassFilter highPassFilterEffect;
|
||||
LowPassFilter lowPassFilterEffect;
|
||||
Echo echoEffect;
|
||||
Reverb reverbEffect;
|
||||
|
||||
const std::array<Effect*, 5> effects{
|
||||
&surroundEffect,
|
||||
const std::array<Effect*, 9> effects{&surroundEffect,
|
||||
&pitchVolumeEffect,
|
||||
&attenuationEffect,
|
||||
&toneEffect,
|
||||
&dopplerEffect,
|
||||
};
|
||||
&highPassFilterEffect,
|
||||
&lowPassFilterEffect,
|
||||
&echoEffect,
|
||||
&reverbEffect};
|
||||
|
||||
std::size_t current = 0;
|
||||
|
||||
|
352
extlibs/headers/stb_image/stb_image.h
vendored
352
extlibs/headers/stb_image/stb_image.h
vendored
@ -1,4 +1,4 @@
|
||||
/* stb_image - v2.28 - public domain image loader - http://nothings.org/stb
|
||||
/* stb_image - v2.29 - public domain image loader - http://nothings.org/stb
|
||||
no warranty implied; use at your own risk
|
||||
|
||||
Do this:
|
||||
@ -48,6 +48,7 @@ LICENSE
|
||||
|
||||
RECENT REVISION HISTORY:
|
||||
|
||||
2.29 (2023-05-xx) optimizations
|
||||
2.28 (2023-01-29) many error fixes, security errors, just tons of stuff
|
||||
2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes
|
||||
2.26 (2020-07-13) many minor fixes
|
||||
@ -1072,8 +1073,8 @@ static int stbi__addints_valid(int a, int b)
|
||||
return a <= INT_MAX - b;
|
||||
}
|
||||
|
||||
// returns 1 if the product of two signed shorts is valid, 0 on overflow.
|
||||
static int stbi__mul2shorts_valid(short a, short b)
|
||||
// returns 1 if the product of two ints fits in a signed short, 0 on overflow.
|
||||
static int stbi__mul2shorts_valid(int a, int b)
|
||||
{
|
||||
if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow
|
||||
if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid
|
||||
@ -3384,13 +3385,13 @@ static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int stbi__skip_jpeg_junk_at_end(stbi__jpeg *j)
|
||||
static stbi_uc stbi__skip_jpeg_junk_at_end(stbi__jpeg *j)
|
||||
{
|
||||
// some JPEGs have junk at end, skip over it but if we find what looks
|
||||
// like a valid marker, resume there
|
||||
while (!stbi__at_eof(j->s)) {
|
||||
int x = stbi__get8(j->s);
|
||||
while (x == 255) { // might be a marker
|
||||
stbi_uc x = stbi__get8(j->s);
|
||||
while (x == 0xff) { // might be a marker
|
||||
if (stbi__at_eof(j->s)) return STBI__MARKER_none;
|
||||
x = stbi__get8(j->s);
|
||||
if (x != 0x00 && x != 0xff) {
|
||||
@ -4176,6 +4177,7 @@ typedef struct
|
||||
{
|
||||
stbi_uc *zbuffer, *zbuffer_end;
|
||||
int num_bits;
|
||||
int hit_zeof_once;
|
||||
stbi__uint32 code_buffer;
|
||||
|
||||
char *zout;
|
||||
@ -4242,10 +4244,21 @@ stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z)
|
||||
int b,s;
|
||||
if (a->num_bits < 16) {
|
||||
if (stbi__zeof(a)) {
|
||||
return -1; /* report error for unexpected end of data. */
|
||||
if (!a->hit_zeof_once) {
|
||||
// This is the first time we hit eof, insert 16 extra padding btis
|
||||
// to allow us to keep going; if we actually consume any of them
|
||||
// though, that is invalid data. This is caught later.
|
||||
a->hit_zeof_once = 1;
|
||||
a->num_bits += 16; // add 16 implicit zero bits
|
||||
} else {
|
||||
// We already inserted our extra 16 padding bits and are again
|
||||
// out, this stream is actually prematurely terminated.
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
stbi__fill_bits(a);
|
||||
}
|
||||
}
|
||||
b = z->fast[a->code_buffer & STBI__ZFAST_MASK];
|
||||
if (b) {
|
||||
s = b >> 9;
|
||||
@ -4309,6 +4322,13 @@ static int stbi__parse_huffman_block(stbi__zbuf *a)
|
||||
int len,dist;
|
||||
if (z == 256) {
|
||||
a->zout = zout;
|
||||
if (a->hit_zeof_once && a->num_bits < 16) {
|
||||
// The first time we hit zeof, we inserted 16 extra zero bits into our bit
|
||||
// buffer so the decoder can just do its speculative decoding. But if we
|
||||
// actually consumed any of those bits (which is the case when num_bits < 16),
|
||||
// the stream actually read past the end so it is malformed.
|
||||
return stbi__err("unexpected end","Corrupt PNG");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data
|
||||
@ -4320,7 +4340,7 @@ static int stbi__parse_huffman_block(stbi__zbuf *a)
|
||||
dist = stbi__zdist_base[z];
|
||||
if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]);
|
||||
if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG");
|
||||
if (zout + len > a->zout_end) {
|
||||
if (len > a->zout_end - zout) {
|
||||
if (!stbi__zexpand(a, zout, len)) return 0;
|
||||
zout = a->zout;
|
||||
}
|
||||
@ -4464,6 +4484,7 @@ static int stbi__parse_zlib(stbi__zbuf *a, int parse_header)
|
||||
if (!stbi__parse_zlib_header(a)) return 0;
|
||||
a->num_bits = 0;
|
||||
a->code_buffer = 0;
|
||||
a->hit_zeof_once = 0;
|
||||
do {
|
||||
final = stbi__zreceive(a,1);
|
||||
type = stbi__zreceive(a,2);
|
||||
@ -4619,9 +4640,8 @@ enum {
|
||||
STBI__F_up=2,
|
||||
STBI__F_avg=3,
|
||||
STBI__F_paeth=4,
|
||||
// synthetic filters used for first scanline to avoid needing a dummy row of 0s
|
||||
STBI__F_avg_first,
|
||||
STBI__F_paeth_first
|
||||
// synthetic filter used for first scanline to avoid needing a dummy row of 0s
|
||||
STBI__F_avg_first
|
||||
};
|
||||
|
||||
static stbi_uc first_row_filter[5] =
|
||||
@ -4630,22 +4650,47 @@ static stbi_uc first_row_filter[5] =
|
||||
STBI__F_sub,
|
||||
STBI__F_none,
|
||||
STBI__F_avg_first,
|
||||
STBI__F_paeth_first
|
||||
STBI__F_sub // Paeth with b=c=0 turns out to be equivalent to sub
|
||||
};
|
||||
|
||||
static int stbi__paeth(int a, int b, int c)
|
||||
{
|
||||
int p = a + b - c;
|
||||
int pa = abs(p-a);
|
||||
int pb = abs(p-b);
|
||||
int pc = abs(p-c);
|
||||
if (pa <= pb && pa <= pc) return a;
|
||||
if (pb <= pc) return b;
|
||||
return c;
|
||||
// This formulation looks very different from the reference in the PNG spec, but is
|
||||
// actually equivalent and has favorable data dependencies and admits straightforward
|
||||
// generation of branch-free code, which helps performance significantly.
|
||||
int thresh = c*3 - (a + b);
|
||||
int lo = a < b ? a : b;
|
||||
int hi = a < b ? b : a;
|
||||
int t0 = (hi <= thresh) ? lo : c;
|
||||
int t1 = (thresh <= lo) ? hi : t0;
|
||||
return t1;
|
||||
}
|
||||
|
||||
static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 };
|
||||
|
||||
// adds an extra all-255 alpha channel
|
||||
// dest == src is legal
|
||||
// img_n must be 1 or 3
|
||||
static void stbi__create_png_alpha_expand8(stbi_uc *dest, stbi_uc *src, stbi__uint32 x, int img_n)
|
||||
{
|
||||
int i;
|
||||
// must process data backwards since we allow dest==src
|
||||
if (img_n == 1) {
|
||||
for (i=x-1; i >= 0; --i) {
|
||||
dest[i*2+1] = 255;
|
||||
dest[i*2+0] = src[i];
|
||||
}
|
||||
} else {
|
||||
STBI_ASSERT(img_n == 3);
|
||||
for (i=x-1; i >= 0; --i) {
|
||||
dest[i*4+3] = 255;
|
||||
dest[i*4+2] = src[i*3+2];
|
||||
dest[i*4+1] = src[i*3+1];
|
||||
dest[i*4+0] = src[i*3+0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create the png data from post-deflated data
|
||||
static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color)
|
||||
{
|
||||
@ -4653,6 +4698,8 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r
|
||||
stbi__context *s = a->s;
|
||||
stbi__uint32 i,j,stride = x*out_n*bytes;
|
||||
stbi__uint32 img_len, img_width_bytes;
|
||||
stbi_uc *filter_buf;
|
||||
int all_ok = 1;
|
||||
int k;
|
||||
int img_n = s->img_n; // copy it into a local for later
|
||||
|
||||
@ -4664,8 +4711,11 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r
|
||||
a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into
|
||||
if (!a->out) return stbi__err("outofmem", "Out of memory");
|
||||
|
||||
// note: error exits here don't need to clean up a->out individually,
|
||||
// stbi__do_png always does on error.
|
||||
if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG");
|
||||
img_width_bytes = (((img_n * x * depth) + 7) >> 3);
|
||||
if (!stbi__mad2sizes_valid(img_width_bytes, y, img_width_bytes)) return stbi__err("too large", "Corrupt PNG");
|
||||
img_len = (img_width_bytes + 1) * y;
|
||||
|
||||
// we used to check for exact match between raw_len and img_len on non-interlaced PNGs,
|
||||
@ -4673,188 +4723,136 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r
|
||||
// so just check for raw_len < img_len always.
|
||||
if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG");
|
||||
|
||||
for (j=0; j < y; ++j) {
|
||||
stbi_uc *cur = a->out + stride*j;
|
||||
stbi_uc *prior;
|
||||
int filter = *raw++;
|
||||
|
||||
if (filter > 4)
|
||||
return stbi__err("invalid filter","Corrupt PNG");
|
||||
// Allocate two scan lines worth of filter workspace buffer.
|
||||
filter_buf = (stbi_uc *) stbi__malloc_mad2(img_width_bytes, 2, 0);
|
||||
if (!filter_buf) return stbi__err("outofmem", "Out of memory");
|
||||
|
||||
// Filtering for low-bit-depth images
|
||||
if (depth < 8) {
|
||||
if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG");
|
||||
cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place
|
||||
filter_bytes = 1;
|
||||
width = img_width_bytes;
|
||||
}
|
||||
prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above
|
||||
|
||||
for (j=0; j < y; ++j) {
|
||||
// cur/prior filter buffers alternate
|
||||
stbi_uc *cur = filter_buf + (j & 1)*img_width_bytes;
|
||||
stbi_uc *prior = filter_buf + (~j & 1)*img_width_bytes;
|
||||
stbi_uc *dest = a->out + stride*j;
|
||||
int nk = width * filter_bytes;
|
||||
int filter = *raw++;
|
||||
|
||||
// check filter type
|
||||
if (filter > 4) {
|
||||
all_ok = stbi__err("invalid filter","Corrupt PNG");
|
||||
break;
|
||||
}
|
||||
|
||||
// if first row, use special filter that doesn't sample previous row
|
||||
if (j == 0) filter = first_row_filter[filter];
|
||||
|
||||
// handle first byte explicitly
|
||||
for (k=0; k < filter_bytes; ++k) {
|
||||
// perform actual filtering
|
||||
switch (filter) {
|
||||
case STBI__F_none : cur[k] = raw[k]; break;
|
||||
case STBI__F_sub : cur[k] = raw[k]; break;
|
||||
case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break;
|
||||
case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break;
|
||||
case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break;
|
||||
case STBI__F_avg_first : cur[k] = raw[k]; break;
|
||||
case STBI__F_paeth_first: cur[k] = raw[k]; break;
|
||||
}
|
||||
}
|
||||
|
||||
if (depth == 8) {
|
||||
if (img_n != out_n)
|
||||
cur[img_n] = 255; // first pixel
|
||||
raw += img_n;
|
||||
cur += out_n;
|
||||
prior += out_n;
|
||||
} else if (depth == 16) {
|
||||
if (img_n != out_n) {
|
||||
cur[filter_bytes] = 255; // first pixel top byte
|
||||
cur[filter_bytes+1] = 255; // first pixel bottom byte
|
||||
}
|
||||
raw += filter_bytes;
|
||||
cur += output_bytes;
|
||||
prior += output_bytes;
|
||||
} else {
|
||||
raw += 1;
|
||||
cur += 1;
|
||||
prior += 1;
|
||||
}
|
||||
|
||||
// this is a little gross, so that we don't switch per-pixel or per-component
|
||||
if (depth < 8 || img_n == out_n) {
|
||||
int nk = (width - 1)*filter_bytes;
|
||||
#define STBI__CASE(f) \
|
||||
case f: \
|
||||
case STBI__F_none:
|
||||
memcpy(cur, raw, nk);
|
||||
break;
|
||||
case STBI__F_sub:
|
||||
memcpy(cur, raw, filter_bytes);
|
||||
for (k = filter_bytes; k < nk; ++k)
|
||||
cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]);
|
||||
break;
|
||||
case STBI__F_up:
|
||||
for (k = 0; k < nk; ++k)
|
||||
switch (filter) {
|
||||
// "none" filter turns into a memcpy here; make that explicit.
|
||||
case STBI__F_none: memcpy(cur, raw, nk); break;
|
||||
STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break;
|
||||
STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;
|
||||
STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break;
|
||||
STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break;
|
||||
STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break;
|
||||
STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break;
|
||||
cur[k] = STBI__BYTECAST(raw[k] + prior[k]);
|
||||
break;
|
||||
case STBI__F_avg:
|
||||
for (k = 0; k < filter_bytes; ++k)
|
||||
cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1));
|
||||
for (k = filter_bytes; k < nk; ++k)
|
||||
cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1));
|
||||
break;
|
||||
case STBI__F_paeth:
|
||||
for (k = 0; k < filter_bytes; ++k)
|
||||
cur[k] = STBI__BYTECAST(raw[k] + prior[k]); // prior[k] == stbi__paeth(0,prior[k],0)
|
||||
for (k = filter_bytes; k < nk; ++k)
|
||||
cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes], prior[k], prior[k-filter_bytes]));
|
||||
break;
|
||||
case STBI__F_avg_first:
|
||||
memcpy(cur, raw, filter_bytes);
|
||||
for (k = filter_bytes; k < nk; ++k)
|
||||
cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1));
|
||||
break;
|
||||
}
|
||||
#undef STBI__CASE
|
||||
|
||||
raw += nk;
|
||||
|
||||
// expand decoded bits in cur to dest, also adding an extra alpha channel if desired
|
||||
if (depth < 8) {
|
||||
stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range
|
||||
stbi_uc *in = cur;
|
||||
stbi_uc *out = dest;
|
||||
stbi_uc inb = 0;
|
||||
stbi__uint32 nsmp = x*img_n;
|
||||
|
||||
// expand bits to bytes first
|
||||
if (depth == 4) {
|
||||
for (i=0; i < nsmp; ++i) {
|
||||
if ((i & 1) == 0) inb = *in++;
|
||||
*out++ = scale * (inb >> 4);
|
||||
inb <<= 4;
|
||||
}
|
||||
} else if (depth == 2) {
|
||||
for (i=0; i < nsmp; ++i) {
|
||||
if ((i & 3) == 0) inb = *in++;
|
||||
*out++ = scale * (inb >> 6);
|
||||
inb <<= 2;
|
||||
}
|
||||
} else {
|
||||
STBI_ASSERT(depth == 1);
|
||||
for (i=0; i < nsmp; ++i) {
|
||||
if ((i & 7) == 0) inb = *in++;
|
||||
*out++ = scale * (inb >> 7);
|
||||
inb <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// insert alpha=255 values if desired
|
||||
if (img_n != out_n)
|
||||
stbi__create_png_alpha_expand8(dest, dest, x, img_n);
|
||||
} else if (depth == 8) {
|
||||
if (img_n == out_n)
|
||||
memcpy(dest, cur, x*img_n);
|
||||
else
|
||||
stbi__create_png_alpha_expand8(dest, cur, x, img_n);
|
||||
} else if (depth == 16) {
|
||||
// convert the image data from big-endian to platform-native
|
||||
stbi__uint16 *dest16 = (stbi__uint16*)dest;
|
||||
stbi__uint32 nsmp = x*img_n;
|
||||
|
||||
if (img_n == out_n) {
|
||||
for (i = 0; i < nsmp; ++i, ++dest16, cur += 2)
|
||||
*dest16 = (cur[0] << 8) | cur[1];
|
||||
} else {
|
||||
STBI_ASSERT(img_n+1 == out_n);
|
||||
#define STBI__CASE(f) \
|
||||
case f: \
|
||||
for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \
|
||||
for (k=0; k < filter_bytes; ++k)
|
||||
switch (filter) {
|
||||
STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break;
|
||||
STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break;
|
||||
STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;
|
||||
STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break;
|
||||
STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break;
|
||||
STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break;
|
||||
STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break;
|
||||
}
|
||||
#undef STBI__CASE
|
||||
|
||||
// the loop above sets the high byte of the pixels' alpha, but for
|
||||
// 16 bit png files we also need the low byte set. we'll do that here.
|
||||
if (depth == 16) {
|
||||
cur = a->out + stride*j; // start at the beginning of the row again
|
||||
for (i=0; i < x; ++i,cur+=output_bytes) {
|
||||
cur[filter_bytes+1] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we make a separate pass to expand bits to pixels; for performance,
|
||||
// this could run two scanlines behind the above code, so it won't
|
||||
// intefere with filtering but will still be in the cache.
|
||||
if (depth < 8) {
|
||||
for (j=0; j < y; ++j) {
|
||||
stbi_uc *cur = a->out + stride*j;
|
||||
stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes;
|
||||
// unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit
|
||||
// png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop
|
||||
stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range
|
||||
|
||||
// note that the final byte might overshoot and write more data than desired.
|
||||
// we can allocate enough data that this never writes out of memory, but it
|
||||
// could also overwrite the next scanline. can it overwrite non-empty data
|
||||
// on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel.
|
||||
// so we need to explicitly clamp the final ones
|
||||
|
||||
if (depth == 4) {
|
||||
for (k=x*img_n; k >= 2; k-=2, ++in) {
|
||||
*cur++ = scale * ((*in >> 4) );
|
||||
*cur++ = scale * ((*in ) & 0x0f);
|
||||
}
|
||||
if (k > 0) *cur++ = scale * ((*in >> 4) );
|
||||
} else if (depth == 2) {
|
||||
for (k=x*img_n; k >= 4; k-=4, ++in) {
|
||||
*cur++ = scale * ((*in >> 6) );
|
||||
*cur++ = scale * ((*in >> 4) & 0x03);
|
||||
*cur++ = scale * ((*in >> 2) & 0x03);
|
||||
*cur++ = scale * ((*in ) & 0x03);
|
||||
}
|
||||
if (k > 0) *cur++ = scale * ((*in >> 6) );
|
||||
if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03);
|
||||
if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03);
|
||||
} else if (depth == 1) {
|
||||
for (k=x*img_n; k >= 8; k-=8, ++in) {
|
||||
*cur++ = scale * ((*in >> 7) );
|
||||
*cur++ = scale * ((*in >> 6) & 0x01);
|
||||
*cur++ = scale * ((*in >> 5) & 0x01);
|
||||
*cur++ = scale * ((*in >> 4) & 0x01);
|
||||
*cur++ = scale * ((*in >> 3) & 0x01);
|
||||
*cur++ = scale * ((*in >> 2) & 0x01);
|
||||
*cur++ = scale * ((*in >> 1) & 0x01);
|
||||
*cur++ = scale * ((*in ) & 0x01);
|
||||
}
|
||||
if (k > 0) *cur++ = scale * ((*in >> 7) );
|
||||
if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01);
|
||||
if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01);
|
||||
if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01);
|
||||
if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01);
|
||||
if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01);
|
||||
if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01);
|
||||
}
|
||||
if (img_n != out_n) {
|
||||
int q;
|
||||
// insert alpha = 255
|
||||
cur = a->out + stride*j;
|
||||
if (img_n == 1) {
|
||||
for (q=x-1; q >= 0; --q) {
|
||||
cur[q*2+1] = 255;
|
||||
cur[q*2+0] = cur[q];
|
||||
for (i = 0; i < x; ++i, dest16 += 2, cur += 2) {
|
||||
dest16[0] = (cur[0] << 8) | cur[1];
|
||||
dest16[1] = 0xffff;
|
||||
}
|
||||
} else {
|
||||
STBI_ASSERT(img_n == 3);
|
||||
for (q=x-1; q >= 0; --q) {
|
||||
cur[q*4+3] = 255;
|
||||
cur[q*4+2] = cur[q*3+2];
|
||||
cur[q*4+1] = cur[q*3+1];
|
||||
cur[q*4+0] = cur[q*3+0];
|
||||
for (i = 0; i < x; ++i, dest16 += 4, cur += 6) {
|
||||
dest16[0] = (cur[0] << 8) | cur[1];
|
||||
dest16[1] = (cur[2] << 8) | cur[3];
|
||||
dest16[2] = (cur[4] << 8) | cur[5];
|
||||
dest16[3] = 0xffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (depth == 16) {
|
||||
// force the image data from big-endian to platform-native.
|
||||
// this is done in a separate pass due to the decoding relying
|
||||
// on the data being untouched, but could probably be done
|
||||
// per-line during decode if care is taken.
|
||||
stbi_uc *cur = a->out;
|
||||
stbi__uint16 *cur16 = (stbi__uint16*)cur;
|
||||
|
||||
for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) {
|
||||
*cur16 = (cur[0] << 8) | cur[1];
|
||||
}
|
||||
}
|
||||
STBI_FREE(filter_buf);
|
||||
if (!all_ok) return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
@ -162,6 +162,17 @@ public:
|
||||
////////////////////////////////////////////////////////////
|
||||
void setPlayingOffset(Time timeOffset);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Set the effect processor to be applied to the sound
|
||||
///
|
||||
/// The effect processor is a callable that will be called
|
||||
/// with sound data to be processed.
|
||||
///
|
||||
/// \param effectProcessor The effect processor to attach to this sound, attach an empty processor to disable processing
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
void setEffectProcessor(EffectProcessor effectProcessor) override;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Get the audio buffer attached to the sound
|
||||
///
|
||||
|
@ -63,7 +63,7 @@ public:
|
||||
/// This function uses its own thread so that it doesn't block
|
||||
/// the rest of the program while the capture runs.
|
||||
/// Please note that only one capture can happen at the same time.
|
||||
/// You can select which capture device will be used, by passing
|
||||
/// You can select which capture device will be used by passing
|
||||
/// the name to the setDevice() method. If none was selected
|
||||
/// before, the default capture device will be used. You can get a
|
||||
/// list of the names of all available capture devices by calling
|
||||
|
@ -32,6 +32,8 @@
|
||||
#include <SFML/System/Angle.hpp>
|
||||
#include <SFML/System/Vector3.hpp>
|
||||
|
||||
#include <functional>
|
||||
|
||||
|
||||
namespace sf
|
||||
{
|
||||
@ -72,6 +74,76 @@ public:
|
||||
float outerGain{}; //!< Outer gain
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Callable that is provided with sound data for processing
|
||||
///
|
||||
/// When the audio engine sources sound data from sound
|
||||
/// sources it will pass the data through an effects
|
||||
/// processor if one is set. The sound data will already be
|
||||
/// converted to the internal floating point format.
|
||||
///
|
||||
/// Sound data that is processed this way is provided in
|
||||
/// frames. Each frame contains 1 floating point sample per
|
||||
/// channel. If e.g. the data source provides stereo data,
|
||||
/// each frame will contain 2 floats.
|
||||
///
|
||||
/// The effects processor function takes 4 parameters:
|
||||
/// - The input data frames, channels interleaved
|
||||
/// - The number of input data frames available
|
||||
/// - The buffer to write output data frames to, channels interleaved
|
||||
/// - The number of output data frames that the output buffer can hold
|
||||
/// - The channel count
|
||||
///
|
||||
/// The input and output frame counts are in/out parameters.
|
||||
///
|
||||
/// When this function is called, the input count will
|
||||
/// contain the number of frames available in the input
|
||||
/// buffer. The output count will contain the size of the
|
||||
/// output buffer i.e. the maximum number of frames that
|
||||
/// can be written to the output buffer.
|
||||
///
|
||||
/// Attempting to read more frames than the input frame
|
||||
/// count or write more frames than the output frame count
|
||||
/// will result in undefined behaviour.
|
||||
///
|
||||
/// When done processing the frames, the input and output
|
||||
/// frame counts must be updated to reflect the actual
|
||||
/// number of frames that were read from the input and
|
||||
/// written to the output.
|
||||
///
|
||||
/// The processing function should always try to process as
|
||||
/// much sound data as possible i.e. always try to fill the
|
||||
/// output buffer to the maximum. In certain situations for
|
||||
/// specific effects it can be possible that the input frame
|
||||
/// count and output frame count aren't equal. As long as
|
||||
/// the frame counts are updated accordingly this is
|
||||
/// perfectly valid.
|
||||
///
|
||||
/// If the audio engine determines that no audio data is
|
||||
/// available from the data source, the input data frames
|
||||
/// pointer is set to nullptr and the input frame count is
|
||||
/// set to 0. In this case it is up to the function to
|
||||
/// decide how to handle the situation. For specific effects
|
||||
/// e.g. Echo/Delay buffered data might still be able to be
|
||||
/// written to the output buffer even if there is no longer
|
||||
/// any input data.
|
||||
///
|
||||
/// An important thing to remember is that this function is
|
||||
/// directly called by the audio engine. Because the audio
|
||||
/// engine runs on an internal thread of its own, make sure
|
||||
/// access to shared data is synchronized appropriately.
|
||||
///
|
||||
/// Because this function is stored by the SoundSource
|
||||
/// object it will be able to be called as long as the
|
||||
/// SoundSource object hasn't yet been destroyed. Make sure
|
||||
/// that any data this function references outlives the
|
||||
/// SoundSource object otherwise use-after-free errors will
|
||||
/// occur.
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
using EffectProcessor = std::function<
|
||||
void(const float* inputFrames, unsigned int& inputFrameCount, float* outputFrames, unsigned int& outputFrameCount, unsigned int frameChannelCount)>;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Copy constructor
|
||||
///
|
||||
@ -329,6 +401,17 @@ public:
|
||||
////////////////////////////////////////////////////////////
|
||||
void setAttenuation(float attenuation);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Set the effect processor to be applied to the sound
|
||||
///
|
||||
/// The effect processor is a callable that will be called
|
||||
/// with sound data to be processed.
|
||||
///
|
||||
/// \param effectProcessor The effect processor to attach to this sound, attach an empty processor to disable processing
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
virtual void setEffectProcessor(EffectProcessor effectProcessor);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Get the pitch of the sound
|
||||
///
|
||||
|
@ -194,6 +194,17 @@ public:
|
||||
////////////////////////////////////////////////////////////
|
||||
bool getLoop() const;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Set the effect processor to be applied to the sound
|
||||
///
|
||||
/// The effect processor is a callable that will be called
|
||||
/// with sound data to be processed.
|
||||
///
|
||||
/// \param effectProcessor The effect processor to attach to this sound, attach an empty processor to disable processing
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
void setEffectProcessor(EffectProcessor effectProcessor) override;
|
||||
|
||||
protected:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Default constructor
|
||||
|
@ -100,11 +100,15 @@ public:
|
||||
/// Before calling this function, the render-texture is in
|
||||
/// an invalid state, thus it is mandatory to call it before
|
||||
/// doing anything with the render-texture.
|
||||
///
|
||||
/// The last parameter, \a settings, is useful if you want to enable
|
||||
/// multi-sampling or use the render-texture for OpenGL rendering that
|
||||
/// requires a depth or stencil buffer. Otherwise it is unnecessary, and
|
||||
/// you should leave this parameter at its default value.
|
||||
///
|
||||
/// After creation, the contents of the render-texture are undefined.
|
||||
/// Call `RenderTexture::clear` first to ensure a single color fill.
|
||||
///
|
||||
/// \param size Width and height of the render-texture
|
||||
/// \param settings Additional settings for the underlying OpenGL texture and context
|
||||
///
|
||||
|
@ -261,7 +261,7 @@ public:
|
||||
/// the \a area rectangle, and to contain 32-bits RGBA pixels.
|
||||
///
|
||||
/// No additional check is performed on the size of the pixel
|
||||
/// array, passing invalid arguments will lead to an undefined
|
||||
/// array. Passing invalid arguments will lead to an undefined
|
||||
/// behavior.
|
||||
///
|
||||
/// This function does nothing if \a pixels is null or if the
|
||||
@ -279,7 +279,7 @@ public:
|
||||
/// \a height arguments, and it must contain 32-bits RGBA pixels.
|
||||
///
|
||||
/// No additional check is performed on the size of the pixel
|
||||
/// array or the bounds of the area to update, passing invalid
|
||||
/// array or the bounds of the area to update. Passing invalid
|
||||
/// arguments will lead to an undefined behavior.
|
||||
///
|
||||
/// This function does nothing if \a pixels is null or if the
|
||||
@ -297,11 +297,12 @@ public:
|
||||
///
|
||||
/// Although the source texture can be smaller than this texture,
|
||||
/// this function is usually used for updating the whole texture.
|
||||
/// The other overload, which has (x, y) additional arguments,
|
||||
/// is more convenient for updating a sub-area of this texture.
|
||||
/// The other overload, which has an additional destination
|
||||
/// argument, is more convenient for updating a sub-area of this
|
||||
/// texture.
|
||||
///
|
||||
/// No additional check is performed on the size of the passed
|
||||
/// texture, passing a texture bigger than this texture
|
||||
/// texture. Passing a texture bigger than this texture
|
||||
/// will lead to an undefined behavior.
|
||||
///
|
||||
/// This function does nothing if either texture was not
|
||||
@ -315,8 +316,8 @@ public:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Update a part of this texture from another texture
|
||||
///
|
||||
/// No additional check is performed on the size of the texture,
|
||||
/// passing an invalid combination of texture size and destination
|
||||
/// No additional check is performed on the size of the texture.
|
||||
/// Passing an invalid combination of texture size and destination
|
||||
/// will lead to an undefined behavior.
|
||||
///
|
||||
/// This function does nothing if either texture was not
|
||||
@ -333,11 +334,12 @@ public:
|
||||
///
|
||||
/// Although the source image can be smaller than the texture,
|
||||
/// this function is usually used for updating the whole texture.
|
||||
/// The other overload, which has (x, y) additional arguments,
|
||||
/// is more convenient for updating a sub-area of the texture.
|
||||
/// The other overload, which has an additional destination
|
||||
/// argument, is more convenient for updating a sub-area of the
|
||||
/// texture.
|
||||
///
|
||||
/// No additional check is performed on the size of the image,
|
||||
/// passing an image bigger than the texture will lead to an
|
||||
/// No additional check is performed on the size of the image.
|
||||
/// Passing an image bigger than the texture will lead to an
|
||||
/// undefined behavior.
|
||||
///
|
||||
/// This function does nothing if the texture was not
|
||||
@ -351,8 +353,8 @@ public:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Update a part of the texture from an image
|
||||
///
|
||||
/// No additional check is performed on the size of the image,
|
||||
/// passing an invalid combination of image size and destination
|
||||
/// No additional check is performed on the size of the image.
|
||||
/// Passing an invalid combination of image size and destination
|
||||
/// will lead to an undefined behavior.
|
||||
///
|
||||
/// This function does nothing if the texture was not
|
||||
@ -369,11 +371,12 @@ public:
|
||||
///
|
||||
/// Although the source window can be smaller than the texture,
|
||||
/// this function is usually used for updating the whole texture.
|
||||
/// The other overload, which has (x, y) additional arguments,
|
||||
/// is more convenient for updating a sub-area of the texture.
|
||||
/// The other overload, which has an additional destination
|
||||
/// argument, is more convenient for updating a sub-area of the
|
||||
/// texture.
|
||||
///
|
||||
/// No additional check is performed on the size of the window,
|
||||
/// passing a window bigger than the texture will lead to an
|
||||
/// No additional check is performed on the size of the window.
|
||||
/// Passing a window bigger than the texture will lead to an
|
||||
/// undefined behavior.
|
||||
///
|
||||
/// This function does nothing if either the texture or the window
|
||||
@ -387,8 +390,8 @@ public:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Update a part of the texture from the contents of a window
|
||||
///
|
||||
/// No additional check is performed on the size of the window,
|
||||
/// passing an invalid combination of window size and destination
|
||||
/// No additional check is performed on the size of the window.
|
||||
/// Passing an invalid combination of window size and destination
|
||||
/// will lead to an undefined behavior.
|
||||
///
|
||||
/// This function does nothing if either the texture or the window
|
||||
|
@ -33,6 +33,8 @@
|
||||
|
||||
#include <SFML/System/Vector2.hpp>
|
||||
|
||||
#include <array>
|
||||
|
||||
|
||||
namespace sf
|
||||
{
|
||||
@ -267,7 +269,7 @@ private:
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
// clang-format off
|
||||
float m_matrix[16]{1.f, 0.f, 0.f, 0.f,
|
||||
std::array<float, 16> m_matrix{1.f, 0.f, 0.f, 0.f,
|
||||
0.f, 1.f, 0.f, 0.f,
|
||||
0.f, 0.f, 1.f, 0.f,
|
||||
0.f, 0.f, 0.f, 1.f}; //!< 4x4 matrix defining the transformation
|
||||
|
@ -55,7 +55,7 @@ constexpr Transform::Transform(float a00, float a01, float a02,
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr const float* Transform::getMatrix() const
|
||||
{
|
||||
return m_matrix;
|
||||
return m_matrix.data();
|
||||
}
|
||||
|
||||
|
||||
@ -133,8 +133,8 @@ constexpr FloatRect Transform::transformRect(const FloatRect& rectangle) const
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr Transform& Transform::combine(const Transform& transform)
|
||||
{
|
||||
const float* a = m_matrix;
|
||||
const float* b = transform.m_matrix;
|
||||
const auto& a = m_matrix;
|
||||
const auto& b = transform.m_matrix;
|
||||
|
||||
// clang-format off
|
||||
*this = Transform(a[0] * b[0] + a[4] * b[1] + a[12] * b[3],
|
||||
|
@ -154,7 +154,7 @@ public:
|
||||
/// the \a created buffer.
|
||||
///
|
||||
/// No additional check is performed on the size of the vertex
|
||||
/// array, passing invalid arguments will lead to undefined
|
||||
/// array. Passing invalid arguments will lead to undefined
|
||||
/// behavior.
|
||||
///
|
||||
/// This function does nothing if \a vertices is null or if the
|
||||
@ -188,7 +188,7 @@ public:
|
||||
/// than the size of the currently created buffer, the update fails.
|
||||
///
|
||||
/// No additional check is performed on the size of the vertex
|
||||
/// array, passing invalid arguments will lead to undefined
|
||||
/// array. Passing invalid arguments will lead to undefined
|
||||
/// behavior.
|
||||
///
|
||||
/// \param vertices Array of vertices to copy to the buffer
|
||||
@ -386,7 +386,7 @@ SFML_GRAPHICS_API void swap(VertexBuffer& left, VertexBuffer& right) noexcept;
|
||||
/// Simultaneous updates to the vertex buffer are not guaranteed to be
|
||||
/// carried out by the driver in any specific order. Updating the same
|
||||
/// region of the buffer from multiple threads will not cause undefined
|
||||
/// behaviour, however the final state of the buffer will be unpredictable.
|
||||
/// behavior, however the final state of the buffer will be unpredictable.
|
||||
///
|
||||
/// Simultaneous updates of distinct non-overlapping regions of the buffer
|
||||
/// are also not guaranteed to complete in a specific order. However, in
|
||||
|
@ -144,20 +144,20 @@ private:
|
||||
friend constexpr Angle radians(float angle);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Construct from a number of degrees
|
||||
/// \brief Construct from a number of radians
|
||||
///
|
||||
/// This function is internal. To construct angle values,
|
||||
/// use sf::radians or sf::degrees instead.
|
||||
///
|
||||
/// \param degrees Angle in degrees
|
||||
/// \param radians Angle in radians
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr explicit Angle(float degrees);
|
||||
constexpr explicit Angle(float radians);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
float m_degrees{}; //!< Angle value stored as degrees
|
||||
float m_radians{}; //!< Angle value stored as radians
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
@ -187,6 +187,7 @@ private:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \relates Angle
|
||||
/// \brief Overload of == operator to compare two angle values
|
||||
/// \note Does not automatically wrap the angle value
|
||||
///
|
||||
/// \param left Left operand (an angle)
|
||||
/// \param right Right operand (an angle)
|
||||
@ -199,6 +200,7 @@ private:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \relates Angle
|
||||
/// \brief Overload of != operator to compare two angle values
|
||||
/// \note Does not automatically wrap the angle value
|
||||
///
|
||||
/// \param left Left operand (an angle)
|
||||
/// \param right Right operand (an angle)
|
||||
@ -211,6 +213,7 @@ private:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \relates Angle
|
||||
/// \brief Overload of < operator to compare two angle values
|
||||
/// \note Does not automatically wrap the angle value
|
||||
///
|
||||
/// \param left Left operand (an angle)
|
||||
/// \param right Right operand (an angle)
|
||||
@ -223,6 +226,7 @@ private:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \relates Angle
|
||||
/// \brief Overload of > operator to compare two angle values
|
||||
/// \note Does not automatically wrap the angle value
|
||||
///
|
||||
/// \param left Left operand (an angle)
|
||||
/// \param right Right operand (an angle)
|
||||
@ -235,6 +239,7 @@ private:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \relates Angle
|
||||
/// \brief Overload of <= operator to compare two angle values
|
||||
/// \note Does not automatically wrap the angle value
|
||||
///
|
||||
/// \param left Left operand (an angle)
|
||||
/// \param right Right operand (an angle)
|
||||
@ -247,6 +252,7 @@ private:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \relates Angle
|
||||
/// \brief Overload of >= operator to compare two angle values
|
||||
/// \note Does not automatically wrap the angle value
|
||||
///
|
||||
/// \param left Left operand (an angle)
|
||||
/// \param right Right operand (an angle)
|
||||
|
@ -35,15 +35,13 @@ namespace sf
|
||||
namespace priv
|
||||
{
|
||||
constexpr float pi = 3.141592654f;
|
||||
constexpr float tau = pi * 2.f;
|
||||
|
||||
constexpr float positiveRemainder(float a, float b)
|
||||
{
|
||||
assert(b > 0.0f && "Cannot calculate remainder with non-positive divisor");
|
||||
assert(b > 0.f && "Cannot calculate remainder with non-positive divisor");
|
||||
const float val = a - static_cast<float>(static_cast<int>(a / b)) * b;
|
||||
if (val >= 0.f)
|
||||
return val;
|
||||
else
|
||||
return val + b;
|
||||
return val >= 0.f ? val : val + b;
|
||||
}
|
||||
} // namespace priv
|
||||
|
||||
@ -55,33 +53,33 @@ constexpr Angle::Angle() = default;
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr float Angle::asDegrees() const
|
||||
{
|
||||
return m_degrees;
|
||||
return m_radians * (180.f / priv::pi);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr float Angle::asRadians() const
|
||||
{
|
||||
return m_degrees * (priv::pi / 180);
|
||||
return m_radians;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr Angle Angle::wrapSigned() const
|
||||
{
|
||||
return degrees(priv::positiveRemainder(m_degrees + 180, 360) - 180);
|
||||
return radians(priv::positiveRemainder(m_radians + priv::pi, priv::tau) - priv::pi);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr Angle Angle::wrapUnsigned() const
|
||||
{
|
||||
return degrees(priv::positiveRemainder(m_degrees, 360));
|
||||
return radians(priv::positiveRemainder(m_radians, priv::tau));
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr Angle::Angle(float degrees) : m_degrees(degrees)
|
||||
constexpr Angle::Angle(float radians) : m_radians(radians)
|
||||
{
|
||||
}
|
||||
|
||||
@ -89,70 +87,70 @@ constexpr Angle::Angle(float degrees) : m_degrees(degrees)
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr Angle degrees(float angle)
|
||||
{
|
||||
return Angle(angle);
|
||||
return Angle(angle * (priv::pi / 180.f));
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr Angle radians(float angle)
|
||||
{
|
||||
return Angle(angle * (180 / priv::pi));
|
||||
return Angle(angle);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr bool operator==(Angle left, Angle right)
|
||||
{
|
||||
return left.asDegrees() == right.asDegrees();
|
||||
return left.asRadians() == right.asRadians();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr bool operator!=(Angle left, Angle right)
|
||||
{
|
||||
return left.asDegrees() != right.asDegrees();
|
||||
return left.asRadians() != right.asRadians();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr bool operator<(Angle left, Angle right)
|
||||
{
|
||||
return left.asDegrees() < right.asDegrees();
|
||||
return left.asRadians() < right.asRadians();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr bool operator>(Angle left, Angle right)
|
||||
{
|
||||
return left.asDegrees() > right.asDegrees();
|
||||
return left.asRadians() > right.asRadians();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr bool operator<=(Angle left, Angle right)
|
||||
{
|
||||
return left.asDegrees() <= right.asDegrees();
|
||||
return left.asRadians() <= right.asRadians();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr bool operator>=(Angle left, Angle right)
|
||||
{
|
||||
return left.asDegrees() >= right.asDegrees();
|
||||
return left.asRadians() >= right.asRadians();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr Angle operator-(Angle right)
|
||||
{
|
||||
return degrees(-right.asDegrees());
|
||||
return radians(-right.asRadians());
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr Angle operator+(Angle left, Angle right)
|
||||
{
|
||||
return degrees(left.asDegrees() + right.asDegrees());
|
||||
return radians(left.asRadians() + right.asRadians());
|
||||
}
|
||||
|
||||
|
||||
@ -166,7 +164,7 @@ constexpr Angle& operator+=(Angle& left, Angle right)
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr Angle operator-(Angle left, Angle right)
|
||||
{
|
||||
return degrees(left.asDegrees() - right.asDegrees());
|
||||
return radians(left.asRadians() - right.asRadians());
|
||||
}
|
||||
|
||||
|
||||
@ -180,7 +178,7 @@ constexpr Angle& operator-=(Angle& left, Angle right)
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr Angle operator*(Angle left, float right)
|
||||
{
|
||||
return degrees(left.asDegrees() * right);
|
||||
return radians(left.asRadians() * right);
|
||||
}
|
||||
|
||||
|
||||
@ -201,15 +199,15 @@ constexpr Angle& operator*=(Angle& left, float right)
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr Angle operator/(Angle left, float right)
|
||||
{
|
||||
assert(right != 0 && "Angle::operator/ cannot divide by 0");
|
||||
return degrees(left.asDegrees() / right);
|
||||
assert(right != 0.f && "Angle::operator/ cannot divide by 0");
|
||||
return radians(left.asRadians() / right);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr Angle& operator/=(Angle& left, float right)
|
||||
{
|
||||
assert(right != 0 && "Angle::operator/= cannot divide by 0");
|
||||
assert(right != 0.f && "Angle::operator/= cannot divide by 0");
|
||||
return left = left / right;
|
||||
}
|
||||
|
||||
@ -217,23 +215,23 @@ constexpr Angle& operator/=(Angle& left, float right)
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr float operator/(Angle left, Angle right)
|
||||
{
|
||||
assert(right.asDegrees() != 0 && "Angle::operator/ cannot divide by 0");
|
||||
return left.asDegrees() / right.asDegrees();
|
||||
assert(right.asRadians() != 0.f && "Angle::operator/ cannot divide by 0");
|
||||
return left.asRadians() / right.asRadians();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr Angle operator%(Angle left, Angle right)
|
||||
{
|
||||
assert(right.asDegrees() != 0 && "Angle::operator% cannot modulus by 0");
|
||||
return degrees(priv::positiveRemainder(left.asDegrees(), right.asDegrees()));
|
||||
assert(right.asRadians() != 0.f && "Angle::operator% cannot modulus by 0");
|
||||
return radians(priv::positiveRemainder(left.asRadians(), right.asRadians()));
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
constexpr Angle& operator%=(Angle& left, Angle right)
|
||||
{
|
||||
assert(right.asDegrees() != 0 && "Angle::operator%= cannot modulus by 0");
|
||||
assert(right.asRadians() != 0.f && "Angle::operator%= cannot modulus by 0");
|
||||
return left = left % right;
|
||||
}
|
||||
|
||||
|
@ -147,8 +147,8 @@ private:
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
#ifdef SFML_SYSTEM_ANDROID
|
||||
std::unique_ptr<priv::ResourceStream> m_file;
|
||||
#else
|
||||
std::unique_ptr<priv::ResourceStream> m_androidFile;
|
||||
#endif
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Deleter for stdio file stream that closes the file stream
|
||||
///
|
||||
@ -159,7 +159,6 @@ private:
|
||||
};
|
||||
|
||||
std::unique_ptr<std::FILE, FileCloser> m_file; //!< stdio file stream
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace sf
|
||||
|
@ -29,6 +29,7 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Config.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <locale>
|
||||
|
||||
#include <cstdint>
|
||||
|
@ -56,7 +56,7 @@ In Utf<8>::decode(In begin, In end, std::uint32_t& output, std::uint32_t replace
|
||||
{
|
||||
// clang-format off
|
||||
// Some useful precomputed data
|
||||
static constexpr int trailing[256] =
|
||||
static constexpr std::array<std::uint8_t, 256> trailing =
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
@ -68,14 +68,14 @@ In Utf<8>::decode(In begin, In end, std::uint32_t& output, std::uint32_t replace
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5
|
||||
};
|
||||
|
||||
static constexpr std::uint32_t offsets[6] =
|
||||
static constexpr std::array<std::uint32_t, 6> offsets =
|
||||
{
|
||||
0x00000000, 0x00003080, 0x000E2080, 0x03C82080, 0xFA082080, 0x82082080
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
// decode the character
|
||||
const int trailingBytes = trailing[static_cast<std::uint8_t>(*begin)];
|
||||
const auto trailingBytes = trailing[static_cast<std::uint8_t>(*begin)];
|
||||
if (trailingBytes < std::distance(begin, end))
|
||||
{
|
||||
output = 0;
|
||||
@ -110,7 +110,7 @@ template <typename Out>
|
||||
Out Utf<8>::encode(std::uint32_t input, Out output, std::uint8_t replacement)
|
||||
{
|
||||
// Some useful precomputed data
|
||||
static constexpr std::uint8_t firstBytes[7] = {0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC};
|
||||
static constexpr std::array<std::uint8_t, 7> firstBytes = {0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC};
|
||||
|
||||
// encode the character
|
||||
if ((input > 0x0010FFFF) || ((input >= 0xD800) && (input <= 0xDBFF)))
|
||||
@ -134,7 +134,7 @@ Out Utf<8>::encode(std::uint32_t input, Out output, std::uint8_t replacement)
|
||||
// clang-format on
|
||||
|
||||
// Extract the bytes to write
|
||||
std::byte bytes[4];
|
||||
std::array<std::byte, 4> bytes{};
|
||||
|
||||
// clang-format off
|
||||
switch (bytestoWrite)
|
||||
@ -147,7 +147,7 @@ Out Utf<8>::encode(std::uint32_t input, Out output, std::uint8_t replacement)
|
||||
// clang-format on
|
||||
|
||||
// Add them to the output
|
||||
output = priv::copy(bytes, bytes + bytestoWrite, output);
|
||||
output = priv::copy(bytes.data(), bytes.data() + bytestoWrite, output);
|
||||
}
|
||||
|
||||
return output;
|
||||
|
@ -240,7 +240,7 @@ private:
|
||||
/// with either loadFromPixels() or loadFromSystem(), the
|
||||
/// cursor can be changed with sf::WindowBase::setMouseCursor().
|
||||
///
|
||||
/// The behaviour is undefined if the cursor is destroyed while
|
||||
/// The behavior is undefined if the cursor is destroyed while
|
||||
/// in use by the window.
|
||||
///
|
||||
/// Usage example:
|
||||
|
@ -329,10 +329,10 @@ private:
|
||||
static constexpr bool isEventType = isInParameterPack<T>(decltype(m_data)());
|
||||
};
|
||||
|
||||
#include <SFML/Window/Event.inl>
|
||||
|
||||
} // namespace sf
|
||||
|
||||
#include <SFML/Window/Event.inl>
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \class sf::Event
|
||||
|
@ -30,6 +30,14 @@
|
||||
// to compile the code within the compiletime conditional when
|
||||
// an incorrect template parameter is provided.
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Window/Event.hpp> // NOLINT(misc-header-include-cycle)
|
||||
|
||||
|
||||
namespace sf
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
template <typename T>
|
||||
Event::Event(const T& t)
|
||||
@ -58,3 +66,5 @@ const T* Event::getIf() const
|
||||
if constexpr (isEventType<T>)
|
||||
return std::get_if<T>(&m_data);
|
||||
}
|
||||
|
||||
} // namespace sf
|
||||
|
@ -44,11 +44,11 @@ namespace sf::Sensor
|
||||
enum class Type
|
||||
{
|
||||
Accelerometer, //!< Measures the raw acceleration (m/s^2)
|
||||
Gyroscope, //!< Measures the raw rotation rates (degrees/s)
|
||||
Gyroscope, //!< Measures the raw rotation rates (radians/s)
|
||||
Magnetometer, //!< Measures the ambient magnetic field (micro-teslas)
|
||||
Gravity, //!< Measures the direction and intensity of gravity, independent of device acceleration (m/s^2)
|
||||
UserAcceleration, //!< Measures the direction and intensity of device acceleration, independent of the gravity (m/s^2)
|
||||
Orientation //!< Measures the absolute 3D orientation (degrees)
|
||||
Orientation //!< Measures the absolute 3D orientation (radians)
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
|
@ -30,8 +30,12 @@
|
||||
#include <SFML/System/Err.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <ostream>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
|
||||
|
||||
namespace sf::priv
|
||||
{
|
||||
@ -66,17 +70,21 @@ AudioDevice::AudioDevice()
|
||||
|
||||
auto contextConfig = ma_context_config_init();
|
||||
contextConfig.pLog = &*m_log;
|
||||
ma_uint32 deviceCount = 0;
|
||||
const auto nullBackend = ma_backend_null;
|
||||
const std::array<const ma_backend*, 2> backendLists{nullptr, &nullBackend};
|
||||
|
||||
if (const auto result = ma_context_init(nullptr, 0, &contextConfig, &*m_context); result != MA_SUCCESS)
|
||||
for (const auto* backendList : backendLists)
|
||||
{
|
||||
// We can set backendCount to 1 since it is ignored when backends is set to nullptr
|
||||
if (const auto result = ma_context_init(backendList, 1, &contextConfig, &*m_context); result != MA_SUCCESS)
|
||||
{
|
||||
m_context.reset();
|
||||
err() << "Failed to initialize the audio context: " << ma_result_description(result) << std::endl;
|
||||
err() << "Failed to initialize the audio playback context: " << ma_result_description(result) << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Count the playback devices
|
||||
ma_uint32 deviceCount = 0;
|
||||
|
||||
if (const auto result = ma_context_get_devices(&*m_context, nullptr, &deviceCount, nullptr, nullptr);
|
||||
result != MA_SUCCESS)
|
||||
{
|
||||
@ -85,12 +93,27 @@ AudioDevice::AudioDevice()
|
||||
}
|
||||
|
||||
// Check if there are audio playback devices available on the system
|
||||
if (deviceCount > 0)
|
||||
break;
|
||||
|
||||
// Warn if no devices were found using the default backend list
|
||||
if (backendList == nullptr)
|
||||
err() << "No audio playback devices available on the system" << std::endl;
|
||||
|
||||
// Clean up the context if we didn't find any devices
|
||||
ma_context_uninit(&*m_context);
|
||||
}
|
||||
|
||||
// If the NULL audio backend also doesn't provide a device we give up
|
||||
if (deviceCount == 0)
|
||||
{
|
||||
err() << "No audio playback devices available on the system" << std::endl;
|
||||
m_context.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_context->backend == ma_backend_null)
|
||||
err() << "Using NULL audio backend for playback" << std::endl;
|
||||
|
||||
// Create the playback device
|
||||
m_playbackDevice.emplace();
|
||||
|
||||
@ -159,11 +182,16 @@ AudioDevice::AudioDevice()
|
||||
m_listenerProperties.upVector.x,
|
||||
m_listenerProperties.upVector.y,
|
||||
m_listenerProperties.upVector.z);
|
||||
|
||||
// Setup cleanup function with `atexit` to work around destruction order issue with
|
||||
// miniaudio internal audio processing threads on Windows
|
||||
const int rc = std::atexit([] { AudioDevice::get().cleanup(); });
|
||||
assert(rc == 0);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
AudioDevice::~AudioDevice()
|
||||
void AudioDevice::cleanup()
|
||||
{
|
||||
// Destroy the engine
|
||||
if (m_engine)
|
||||
@ -286,8 +314,8 @@ void AudioDevice::setCone(const Listener::Cone& cone)
|
||||
|
||||
ma_engine_listener_set_cone(&*m_engine,
|
||||
0,
|
||||
std::clamp(cone.innerAngle.asRadians(), 0.f, sf::degrees(360).asRadians()),
|
||||
std::clamp(cone.outerAngle.asRadians(), 0.f, sf::degrees(360).asRadians()),
|
||||
std::clamp(cone.innerAngle, Angle::Zero, degrees(360.f)).asRadians(),
|
||||
std::clamp(cone.outerAngle, Angle::Zero, degrees(360.f)).asRadians(),
|
||||
cone.outerGain);
|
||||
}
|
||||
|
||||
|
@ -54,10 +54,10 @@ private:
|
||||
AudioDevice();
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Destructor
|
||||
/// \brief Cleanup function, called via `atexit`
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
~AudioDevice();
|
||||
void cleanup();
|
||||
|
||||
public:
|
||||
////////////////////////////////////////////////////////////
|
||||
@ -227,11 +227,11 @@ private:
|
||||
struct ListenerProperties
|
||||
{
|
||||
float volume{100.f};
|
||||
sf::Vector3f position{0, 0, 0};
|
||||
sf::Vector3f direction{0, 0, -1};
|
||||
sf::Vector3f velocity{0, 0, 0};
|
||||
Listener::Cone cone{sf::degrees(360), sf::degrees(360), 1};
|
||||
sf::Vector3f upVector{0, 1, 0};
|
||||
Vector3f position{0, 0, 0};
|
||||
Vector3f direction{0, 0, -1};
|
||||
Vector3f velocity{0, 0, 0};
|
||||
Listener::Cone cone{degrees(360.f), degrees(360.f), 1};
|
||||
Vector3f upVector{0, 1, 0};
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
|
@ -82,7 +82,7 @@ sfml_add_library(Audio
|
||||
target_compile_definitions(sfml-audio PRIVATE OV_EXCLUDE_STATIC_CALLBACKS FLAC__NO_DLL)
|
||||
|
||||
# disable miniaudio features we do not use
|
||||
target_compile_definitions(sfml-audio PRIVATE MA_NO_DECODING MA_NO_ENCODING MA_NO_RESOURCE_MANAGER MA_NO_GENERATION)
|
||||
target_compile_definitions(sfml-audio PRIVATE MA_NO_MP3 MA_NO_FLAC MA_NO_ENCODING MA_NO_RESOURCE_MANAGER MA_NO_GENERATION)
|
||||
|
||||
# setup dependencies
|
||||
target_link_libraries(sfml-audio
|
||||
|
@ -63,8 +63,8 @@ struct SavedSettings
|
||||
float minGain{0.f};
|
||||
float maxGain{1.f};
|
||||
float rollOff{1.f};
|
||||
float innerAngle{sf::degrees(360).asRadians()};
|
||||
float outerAngle{sf::degrees(360).asRadians()};
|
||||
float innerAngle{degrees(360.f).asRadians()};
|
||||
float outerAngle{degrees(360.f).asRadians()};
|
||||
float outerGain{0.f};
|
||||
};
|
||||
|
||||
@ -135,7 +135,7 @@ void initializeDataSource(ma_data_source_base& dataSourceBase, const ma_data_sou
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
ma_channel MiniaudioUtils::soundChannelToMiniaudioChannel(sf::SoundChannel soundChannel)
|
||||
ma_channel MiniaudioUtils::soundChannelToMiniaudioChannel(SoundChannel soundChannel)
|
||||
{
|
||||
switch (soundChannel)
|
||||
{
|
||||
@ -184,6 +184,56 @@ ma_channel MiniaudioUtils::soundChannelToMiniaudioChannel(sf::SoundChannel sound
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
SoundChannel MiniaudioUtils::miniaudioChannelToSoundChannel(ma_channel soundChannel)
|
||||
{
|
||||
switch (soundChannel)
|
||||
{
|
||||
case MA_CHANNEL_NONE:
|
||||
return SoundChannel::Unspecified;
|
||||
case MA_CHANNEL_MONO:
|
||||
return SoundChannel::Mono;
|
||||
case MA_CHANNEL_FRONT_LEFT:
|
||||
return SoundChannel::FrontLeft;
|
||||
case MA_CHANNEL_FRONT_RIGHT:
|
||||
return SoundChannel::FrontRight;
|
||||
case MA_CHANNEL_FRONT_CENTER:
|
||||
return SoundChannel::FrontCenter;
|
||||
case MA_CHANNEL_FRONT_LEFT_CENTER:
|
||||
return SoundChannel::FrontLeftOfCenter;
|
||||
case MA_CHANNEL_FRONT_RIGHT_CENTER:
|
||||
return SoundChannel::FrontRightOfCenter;
|
||||
case MA_CHANNEL_LFE:
|
||||
return SoundChannel::LowFrequencyEffects;
|
||||
case MA_CHANNEL_BACK_LEFT:
|
||||
return SoundChannel::BackLeft;
|
||||
case MA_CHANNEL_BACK_RIGHT:
|
||||
return SoundChannel::BackRight;
|
||||
case MA_CHANNEL_BACK_CENTER:
|
||||
return SoundChannel::BackCenter;
|
||||
case MA_CHANNEL_SIDE_LEFT:
|
||||
return SoundChannel::SideLeft;
|
||||
case MA_CHANNEL_SIDE_RIGHT:
|
||||
return SoundChannel::SideRight;
|
||||
case MA_CHANNEL_TOP_CENTER:
|
||||
return SoundChannel::TopCenter;
|
||||
case MA_CHANNEL_TOP_FRONT_LEFT:
|
||||
return SoundChannel::TopFrontLeft;
|
||||
case MA_CHANNEL_TOP_FRONT_RIGHT:
|
||||
return SoundChannel::TopFrontRight;
|
||||
case MA_CHANNEL_TOP_FRONT_CENTER:
|
||||
return SoundChannel::TopFrontCenter;
|
||||
case MA_CHANNEL_TOP_BACK_LEFT:
|
||||
return SoundChannel::TopBackLeft;
|
||||
case MA_CHANNEL_TOP_BACK_RIGHT:
|
||||
return SoundChannel::TopBackRight;
|
||||
default:
|
||||
assert(soundChannel == MA_CHANNEL_TOP_BACK_CENTER);
|
||||
return SoundChannel::TopBackCenter;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Time MiniaudioUtils::getPlayingOffset(ma_sound& sound)
|
||||
{
|
||||
|
@ -41,11 +41,11 @@
|
||||
namespace sf
|
||||
{
|
||||
class Time;
|
||||
}
|
||||
|
||||
namespace sf::priv::MiniaudioUtils
|
||||
namespace priv::MiniaudioUtils
|
||||
{
|
||||
[[nodiscard]] ma_channel soundChannelToMiniaudioChannel(sf::SoundChannel soundChannel);
|
||||
[[nodiscard]] ma_channel soundChannelToMiniaudioChannel(SoundChannel soundChannel);
|
||||
[[nodiscard]] SoundChannel miniaudioChannelToSoundChannel(ma_channel soundChannel);
|
||||
[[nodiscard]] Time getPlayingOffset(ma_sound& sound);
|
||||
[[nodiscard]] ma_uint64 getFrameIndex(ma_sound& sound, Time timeOffset);
|
||||
|
||||
@ -54,4 +54,5 @@ void initializeSound(const ma_data_source_vtable& vtable,
|
||||
ma_data_source_base& dataSourceBase,
|
||||
ma_sound& sound,
|
||||
const std::function<void()>& initializeFn);
|
||||
} // namespace sf::priv::MiniaudioUtils
|
||||
} // namespace priv::MiniaudioUtils
|
||||
} // namespace sf
|
||||
|
@ -55,6 +55,7 @@ struct Sound::Impl
|
||||
~Impl()
|
||||
{
|
||||
ma_sound_uninit(&sound);
|
||||
ma_node_uninit(&effectNode, nullptr);
|
||||
ma_data_source_uninit(&dataSourceBase);
|
||||
}
|
||||
|
||||
@ -90,6 +91,34 @@ struct Sound::Impl
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the custom effect node
|
||||
effectNodeVTable.onProcess =
|
||||
[](ma_node* node, const float** framesIn, ma_uint32* frameCountIn, float** framesOut, ma_uint32* frameCountOut)
|
||||
{ static_cast<EffectNode*>(node)->impl->processEffect(framesIn, *frameCountIn, framesOut, *frameCountOut); };
|
||||
effectNodeVTable.onGetRequiredInputFrameCount = nullptr;
|
||||
effectNodeVTable.inputBusCount = 1;
|
||||
effectNodeVTable.outputBusCount = 1;
|
||||
effectNodeVTable.flags = MA_NODE_FLAG_CONTINUOUS_PROCESSING | MA_NODE_FLAG_ALLOW_NULL_INPUT;
|
||||
|
||||
const auto nodeChannelCount = ma_engine_get_channels(engine);
|
||||
ma_node_config nodeConfig = ma_node_config_init();
|
||||
nodeConfig.vtable = &effectNodeVTable;
|
||||
nodeConfig.pInputChannels = &nodeChannelCount;
|
||||
nodeConfig.pOutputChannels = &nodeChannelCount;
|
||||
|
||||
if (const ma_result result = ma_node_init(ma_engine_get_node_graph(engine), &nodeConfig, nullptr, &effectNode);
|
||||
result != MA_SUCCESS)
|
||||
{
|
||||
err() << "Failed to initialize effect node: " << ma_result_description(result) << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
effectNode.impl = this;
|
||||
effectNode.channelCount = nodeChannelCount;
|
||||
|
||||
// Route the sound through the effect node depending on whether an effect processor is set
|
||||
connectEffect(bool{effectProcessor});
|
||||
|
||||
// Because we are providing a custom data source, we have to provide the channel map ourselves
|
||||
if (buffer && !buffer->getChannelMap().empty())
|
||||
{
|
||||
@ -110,7 +139,80 @@ struct Sound::Impl
|
||||
|
||||
void reinitialize()
|
||||
{
|
||||
priv::MiniaudioUtils::reinitializeSound(sound, [this] { initialize(); });
|
||||
priv::MiniaudioUtils::reinitializeSound(sound,
|
||||
[this]
|
||||
{
|
||||
ma_node_uninit(&effectNode, nullptr);
|
||||
initialize();
|
||||
});
|
||||
}
|
||||
|
||||
void processEffect(const float** framesIn, ma_uint32& frameCountIn, float** framesOut, ma_uint32& frameCountOut) const
|
||||
{
|
||||
// If a processor is set, call it
|
||||
if (effectProcessor)
|
||||
{
|
||||
if (!framesIn)
|
||||
frameCountIn = 0;
|
||||
|
||||
effectProcessor(framesIn ? framesIn[0] : nullptr, frameCountIn, framesOut[0], frameCountOut, effectNode.channelCount);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise just pass the data through 1:1
|
||||
if (framesIn == nullptr)
|
||||
{
|
||||
frameCountIn = 0;
|
||||
frameCountOut = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto toProcess = std::min(frameCountIn, frameCountOut);
|
||||
std::memcpy(framesOut[0], framesIn[0], toProcess * effectNode.channelCount * sizeof(float));
|
||||
frameCountIn = toProcess;
|
||||
frameCountOut = toProcess;
|
||||
}
|
||||
|
||||
void connectEffect(bool connect)
|
||||
{
|
||||
auto* engine = priv::AudioDevice::get().getEngine();
|
||||
|
||||
if (engine == nullptr)
|
||||
{
|
||||
err() << "Failed to connect effect: No engine available" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (connect)
|
||||
{
|
||||
// Attach the custom effect node output to our engine endpoint
|
||||
if (const ma_result result = ma_node_attach_output_bus(&effectNode, 0, ma_engine_get_endpoint(engine), 0);
|
||||
result != MA_SUCCESS)
|
||||
{
|
||||
err() << "Failed to attach effect node output to endpoint: " << ma_result_description(result) << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Detach the custom effect node output from our engine endpoint
|
||||
if (const ma_result result = ma_node_detach_output_bus(&effectNode, 0); result != MA_SUCCESS)
|
||||
{
|
||||
err() << "Failed to detach effect node output from endpoint: " << ma_result_description(result)
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Attach the sound output to the custom effect node or the engine endpoint
|
||||
if (const ma_result
|
||||
result = ma_node_attach_output_bus(&sound, 0, connect ? &effectNode : ma_engine_get_endpoint(engine), 0);
|
||||
result != MA_SUCCESS)
|
||||
{
|
||||
err() << "Failed to attach sound node output to effect node: " << ma_result_description(result) << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static ma_result read(ma_data_source* dataSource, void* framesOut, ma_uint64 frameCount, ma_uint64* framesRead)
|
||||
@ -208,13 +310,23 @@ struct Sound::Impl
|
||||
////////////////////////////////////////////////////////////
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
struct EffectNode
|
||||
{
|
||||
ma_node_base base{};
|
||||
Impl* impl{};
|
||||
ma_uint32 channelCount{};
|
||||
};
|
||||
|
||||
ma_data_source_base dataSourceBase{}; //!< The struct that makes this object a miniaudio data source (must be first member)
|
||||
ma_node_vtable effectNodeVTable{}; //!< Vtable of the effect node
|
||||
EffectNode effectNode; //!< The engine node that performs effect processing
|
||||
std::vector<ma_channel> soundChannelMap; //!< The map of position in sample frame to sound channel (miniaudio channels)
|
||||
ma_sound sound{}; //!< The sound
|
||||
std::size_t cursor{}; //!< The current playing position
|
||||
bool looping{}; //!< True if we are looping the sound
|
||||
const SoundBuffer* buffer{}; //!< Sound buffer bound to the source
|
||||
Status status{Status::Stopped}; //!< The status
|
||||
EffectProcessor effectProcessor; //!< The effect processor
|
||||
};
|
||||
|
||||
|
||||
@ -334,6 +446,14 @@ void Sound::setPlayingOffset(Time timeOffset)
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Sound::setEffectProcessor(EffectProcessor effectProcessor)
|
||||
{
|
||||
m_impl->effectProcessor = std::move(effectProcessor);
|
||||
m_impl->connectEffect(bool{m_impl->effectProcessor});
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
const SoundBuffer& Sound::getBuffer() const
|
||||
{
|
||||
|
@ -69,7 +69,7 @@ public:
|
||||
/// \return Properties of the loaded sound if the file was successfully opened
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] std::optional<Info> open(sf::InputStream& stream) override;
|
||||
[[nodiscard]] std::optional<Info> open(InputStream& stream) override;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Change the current read position to the given sample offset
|
||||
|
@ -142,16 +142,16 @@ std::optional<SoundFileReader::Info> SoundFileReaderMp3::open(InputStream& strea
|
||||
switch (info.channelCount)
|
||||
{
|
||||
case 0:
|
||||
sf::err() << "No channels in MP3 file" << std::endl;
|
||||
err() << "No channels in MP3 file" << std::endl;
|
||||
break;
|
||||
case 1:
|
||||
info.channelMap = {sf::SoundChannel::Mono};
|
||||
info.channelMap = {SoundChannel::Mono};
|
||||
break;
|
||||
case 2:
|
||||
info.channelMap = {sf::SoundChannel::SideLeft, sf::SoundChannel::SideRight};
|
||||
info.channelMap = {SoundChannel::SideLeft, SoundChannel::SideRight};
|
||||
break;
|
||||
default:
|
||||
sf::err() << "MP3 files with more than 2 channels not supported" << std::endl;
|
||||
err() << "MP3 files with more than 2 channels not supported" << std::endl;
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
@ -25,82 +25,64 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
// Headers
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Audio/MiniaudioUtils.hpp>
|
||||
#include <SFML/Audio/SoundFileReaderWav.hpp>
|
||||
|
||||
#include <SFML/System/Err.hpp>
|
||||
#include <SFML/System/InputStream.hpp>
|
||||
#include <SFML/System/Utils.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <ostream>
|
||||
#include <vector>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
// The following functions read integers as little endian and
|
||||
// return them in the host byte order
|
||||
|
||||
bool decode(sf::InputStream& stream, std::uint8_t& value)
|
||||
ma_result onRead(ma_decoder* decoder, void* buffer, size_t bytesToRead, size_t* bytesRead)
|
||||
{
|
||||
return static_cast<std::size_t>(stream.read(&value, sizeof(value))) == sizeof(value);
|
||||
auto* stream = static_cast<sf::InputStream*>(decoder->pUserData);
|
||||
const auto count = stream->read(buffer, static_cast<std::int64_t>(bytesToRead));
|
||||
|
||||
if (count < 0)
|
||||
return MA_ERROR;
|
||||
|
||||
*bytesRead = static_cast<size_t>(count);
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
bool decode(sf::InputStream& stream, std::int16_t& value)
|
||||
ma_result onSeek(ma_decoder* decoder, ma_int64 byteOffset, ma_seek_origin origin)
|
||||
{
|
||||
std::byte bytes[sizeof(value)];
|
||||
if (static_cast<std::size_t>(stream.read(bytes, static_cast<std::int64_t>(sizeof(bytes)))) != sizeof(bytes))
|
||||
return false;
|
||||
auto* stream = static_cast<sf::InputStream*>(decoder->pUserData);
|
||||
|
||||
value = sf::toInteger<std::int16_t>(bytes[0], bytes[1]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool decode(sf::InputStream& stream, std::uint16_t& value)
|
||||
switch (origin)
|
||||
{
|
||||
std::byte bytes[sizeof(value)];
|
||||
if (static_cast<std::size_t>(stream.read(bytes, static_cast<std::int64_t>(sizeof(bytes)))) != sizeof(bytes))
|
||||
return false;
|
||||
|
||||
value = sf::toInteger<std::uint16_t>(bytes[0], bytes[1]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool decode24bit(sf::InputStream& stream, std::uint32_t& value)
|
||||
case ma_seek_origin_start:
|
||||
{
|
||||
std::byte bytes[3];
|
||||
if (static_cast<std::size_t>(stream.read(bytes, static_cast<std::int64_t>(sizeof(bytes)))) != sizeof(bytes))
|
||||
return false;
|
||||
if (stream->seek(byteOffset) < 0)
|
||||
return MA_ERROR;
|
||||
|
||||
value = sf::toInteger<std::uint32_t>(bytes[0], bytes[1], bytes[2]);
|
||||
|
||||
return true;
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
bool decode(sf::InputStream& stream, std::uint32_t& value)
|
||||
case ma_seek_origin_current:
|
||||
{
|
||||
std::byte bytes[sizeof(value)];
|
||||
if (static_cast<std::size_t>(stream.read(bytes, static_cast<std::int64_t>(sizeof(bytes)))) != sizeof(bytes))
|
||||
return false;
|
||||
const auto currentPosition = stream->tell();
|
||||
|
||||
value = sf::toInteger<std::uint32_t>(bytes[0], bytes[1], bytes[2], bytes[3]);
|
||||
if (currentPosition < 0)
|
||||
return MA_ERROR;
|
||||
|
||||
return true;
|
||||
if (stream->seek(stream->tell() + byteOffset) < 0)
|
||||
return MA_ERROR;
|
||||
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
// According to miniaudio comments, ma_seek_origin_end is not used by decoders
|
||||
default:
|
||||
return MA_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
const std::uint64_t mainChunkSize = 12;
|
||||
|
||||
const std::uint16_t waveFormatPcm = 1;
|
||||
|
||||
const std::uint16_t waveFormatExtensible = 65534;
|
||||
|
||||
const char* waveSubformatPcm =
|
||||
"\x01\x00\x00\x00\x00\x00\x10\x00"
|
||||
"\x80\x00\x00\xAA\x00\x38\x9B\x71";
|
||||
} // namespace
|
||||
|
||||
namespace sf::priv
|
||||
@ -108,331 +90,114 @@ namespace sf::priv
|
||||
////////////////////////////////////////////////////////////
|
||||
bool SoundFileReaderWav::check(InputStream& stream)
|
||||
{
|
||||
char header[mainChunkSize];
|
||||
if (stream.read(header, sizeof(header)) < static_cast<std::int64_t>(sizeof(header)))
|
||||
return false;
|
||||
auto config = ma_decoder_config_init_default();
|
||||
config.encodingFormat = ma_encoding_format_wav;
|
||||
config.format = ma_format_s16;
|
||||
ma_decoder decoder{};
|
||||
|
||||
return (header[0] == 'R') && (header[1] == 'I') && (header[2] == 'F') && (header[3] == 'F') && (header[8] == 'W') &&
|
||||
(header[9] == 'A') && (header[10] == 'V') && (header[11] == 'E');
|
||||
if (ma_decoder_init(&onRead, &onSeek, &stream, &config, &decoder) == MA_SUCCESS)
|
||||
{
|
||||
ma_decoder_uninit(&decoder);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
SoundFileReaderWav::~SoundFileReaderWav()
|
||||
{
|
||||
if (m_decoder)
|
||||
{
|
||||
if (const ma_result result = ma_decoder_uninit(&*m_decoder); result != MA_SUCCESS)
|
||||
err() << "Failed to uninitialize wav decoder: " << ma_result_description(result) << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
std::optional<SoundFileReader::Info> SoundFileReaderWav::open(InputStream& stream)
|
||||
{
|
||||
m_stream = &stream;
|
||||
if (m_decoder)
|
||||
{
|
||||
if (const ma_result result = ma_decoder_uninit(&*m_decoder); result != MA_SUCCESS)
|
||||
{
|
||||
err() << "Failed to uninitialize wav decoder: " << ma_result_description(result) << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_decoder.emplace();
|
||||
}
|
||||
|
||||
auto info = parseHeader();
|
||||
if (!info)
|
||||
err() << "Failed to open WAV sound file (invalid or unsupported file)" << std::endl;
|
||||
auto config = ma_decoder_config_init_default();
|
||||
config.encodingFormat = ma_encoding_format_wav;
|
||||
config.format = ma_format_s16;
|
||||
|
||||
return info;
|
||||
if (const ma_result result = ma_decoder_init(&onRead, &onSeek, &stream, &config, &*m_decoder); result != MA_SUCCESS)
|
||||
{
|
||||
err() << "Failed to initialize wav decoder: " << ma_result_description(result) << std::endl;
|
||||
m_decoder = std::nullopt;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ma_uint64 frameCount{};
|
||||
if (const ma_result result = ma_decoder_get_available_frames(&*m_decoder, &frameCount); result != MA_SUCCESS)
|
||||
{
|
||||
err() << "Failed to get available frames from wav decoder: " << ma_result_description(result) << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto format = ma_format_unknown;
|
||||
ma_uint32 sampleRate{};
|
||||
std::array<ma_channel, 20> channelMap{};
|
||||
if (const ma_result result = ma_decoder_get_data_format(&*m_decoder,
|
||||
&format,
|
||||
&m_channelCount,
|
||||
&sampleRate,
|
||||
channelMap.data(),
|
||||
channelMap.size());
|
||||
result != MA_SUCCESS)
|
||||
{
|
||||
err() << "Failed to get data format from wav decoder: " << ma_result_description(result) << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<SoundChannel> soundChannels;
|
||||
soundChannels.reserve(m_channelCount);
|
||||
|
||||
for (auto i = 0u; i < m_channelCount; ++i)
|
||||
soundChannels.emplace_back(priv::MiniaudioUtils::miniaudioChannelToSoundChannel(channelMap[i]));
|
||||
|
||||
return Info{frameCount * m_channelCount, m_channelCount, sampleRate, std::move(soundChannels)};
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void SoundFileReaderWav::seek(std::uint64_t sampleOffset)
|
||||
{
|
||||
assert(m_stream && "Input stream cannot be null. Call SoundFileReaderWav::open() to initialize it.");
|
||||
assert(m_decoder && "wav decoder not initialized. Call SoundFileReaderWav::open() to initialize it.");
|
||||
|
||||
if (m_stream->seek(static_cast<std::int64_t>(m_dataStart + sampleOffset * m_bytesPerSample) == -1))
|
||||
err() << "Failed to seek WAV sound stream" << std::endl;
|
||||
if (const ma_result result = ma_decoder_seek_to_pcm_frame(&*m_decoder, sampleOffset / m_channelCount);
|
||||
result != MA_SUCCESS)
|
||||
err() << "Failed to seek wav sound stream: " << ma_result_description(result) << std::endl;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
std::uint64_t SoundFileReaderWav::read(std::int16_t* samples, std::uint64_t maxCount)
|
||||
{
|
||||
assert(m_stream && "Input stream cannot be null. Call SoundFileReaderWav::open() to initialize it.");
|
||||
assert(m_decoder && "wav decoder not initialized. Call SoundFileReaderWav::open() to initialize it.");
|
||||
|
||||
std::uint64_t count = 0;
|
||||
const auto startPos = static_cast<std::uint64_t>(m_stream->tell());
|
||||
ma_uint64 framesRead{};
|
||||
|
||||
// Tracking of m_dataEnd is important to prevent sf::Music from reading
|
||||
// data until EOF, as WAV files may have metadata at the end.
|
||||
while ((count < maxCount) && (startPos + count * m_bytesPerSample < m_dataEnd))
|
||||
{
|
||||
switch (m_bytesPerSample)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
std::uint8_t sample = 0;
|
||||
if (decode(*m_stream, sample))
|
||||
*samples++ = static_cast<std::int16_t>((static_cast<std::int16_t>(sample) - 128) << 8);
|
||||
else
|
||||
return count;
|
||||
break;
|
||||
}
|
||||
if (const ma_result result = ma_decoder_read_pcm_frames(&*m_decoder, samples, maxCount / m_channelCount, &framesRead);
|
||||
result != MA_SUCCESS)
|
||||
err() << "Failed to read from wav sound stream: " << ma_result_description(result) << std::endl;
|
||||
|
||||
case 2:
|
||||
{
|
||||
std::int16_t sample = 0;
|
||||
if (decode(*m_stream, sample))
|
||||
*samples++ = sample;
|
||||
else
|
||||
return count;
|
||||
break;
|
||||
}
|
||||
|
||||
case 3:
|
||||
{
|
||||
std::uint32_t sample = 0;
|
||||
if (decode24bit(*m_stream, sample))
|
||||
*samples++ = static_cast<std::int16_t>(sample >> 8);
|
||||
else
|
||||
return count;
|
||||
break;
|
||||
}
|
||||
|
||||
case 4:
|
||||
{
|
||||
std::uint32_t sample = 0;
|
||||
if (decode(*m_stream, sample))
|
||||
*samples++ = static_cast<std::int16_t>(sample >> 16);
|
||||
else
|
||||
return count;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
assert(false && "Invalid bytes per sample. Must be 1, 2, 3, or 4.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
++count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
std::optional<SoundFileReader::Info> SoundFileReaderWav::parseHeader()
|
||||
{
|
||||
assert(m_stream && "Input stream cannot be null. Call SoundFileReaderWav::open() to initialize it.");
|
||||
|
||||
// If we are here, it means that the first part of the header
|
||||
// (the format) has already been checked
|
||||
char mainChunk[mainChunkSize];
|
||||
if (static_cast<std::size_t>(m_stream->read(mainChunk, static_cast<std::int64_t>(sizeof(mainChunk)))) !=
|
||||
sizeof(mainChunk))
|
||||
return std::nullopt;
|
||||
|
||||
// Parse all the sub-chunks
|
||||
Info info;
|
||||
bool dataChunkFound = false;
|
||||
while (!dataChunkFound)
|
||||
{
|
||||
// Parse the sub-chunk id and size
|
||||
char subChunkId[4];
|
||||
if (static_cast<std::size_t>(m_stream->read(subChunkId, static_cast<std::int64_t>(sizeof(subChunkId)))) !=
|
||||
sizeof(subChunkId))
|
||||
return std::nullopt;
|
||||
std::uint32_t subChunkSize = 0;
|
||||
if (!decode(*m_stream, subChunkSize))
|
||||
return std::nullopt;
|
||||
const std::int64_t subChunkStart = m_stream->tell();
|
||||
if (subChunkStart == -1)
|
||||
return std::nullopt;
|
||||
|
||||
// Check which chunk it is
|
||||
if ((subChunkId[0] == 'f') && (subChunkId[1] == 'm') && (subChunkId[2] == 't') && (subChunkId[3] == ' '))
|
||||
{
|
||||
// "fmt" chunk
|
||||
|
||||
// Audio format
|
||||
std::uint16_t format = 0;
|
||||
if (!decode(*m_stream, format))
|
||||
return std::nullopt;
|
||||
if ((format != waveFormatPcm) && (format != waveFormatExtensible))
|
||||
return std::nullopt;
|
||||
|
||||
// Channel count
|
||||
std::uint16_t channelCount = 0;
|
||||
if (!decode(*m_stream, channelCount))
|
||||
return std::nullopt;
|
||||
info.channelCount = channelCount;
|
||||
|
||||
// Sample rate
|
||||
std::uint32_t sampleRate = 0;
|
||||
if (!decode(*m_stream, sampleRate))
|
||||
return std::nullopt;
|
||||
info.sampleRate = sampleRate;
|
||||
|
||||
// Byte rate
|
||||
std::uint32_t byteRate = 0;
|
||||
if (!decode(*m_stream, byteRate))
|
||||
return std::nullopt;
|
||||
|
||||
// Block align
|
||||
std::uint16_t blockAlign = 0;
|
||||
if (!decode(*m_stream, blockAlign))
|
||||
return std::nullopt;
|
||||
|
||||
// Bits per sample
|
||||
std::uint16_t bitsPerSample = 0;
|
||||
if (!decode(*m_stream, bitsPerSample))
|
||||
return std::nullopt;
|
||||
if (bitsPerSample != 8 && bitsPerSample != 16 && bitsPerSample != 24 && bitsPerSample != 32)
|
||||
{
|
||||
err() << "Unsupported sample size: " << bitsPerSample
|
||||
<< " bit (Supported sample sizes are 8/16/24/32 bit)" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
m_bytesPerSample = bitsPerSample / 8;
|
||||
|
||||
if (format == waveFormatExtensible)
|
||||
{
|
||||
// Extension size
|
||||
std::uint16_t extensionSize = 0;
|
||||
if (!decode(*m_stream, extensionSize))
|
||||
return std::nullopt;
|
||||
|
||||
// Valid bits per sample
|
||||
std::uint16_t validBitsPerSample = 0;
|
||||
if (!decode(*m_stream, validBitsPerSample))
|
||||
return std::nullopt;
|
||||
|
||||
// Channel mask
|
||||
std::uint32_t channelMask = 0;
|
||||
if (!decode(*m_stream, channelMask))
|
||||
return std::nullopt;
|
||||
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
// For WAVE channel mapping refer to: https://learn.microsoft.com/en-us/previous-versions/windows/hardware/design/dn653308(v=vs.85)#default-channel-ordering
|
||||
static constexpr auto SPEAKER_FRONT_LEFT = 0x1u;
|
||||
static constexpr auto SPEAKER_FRONT_RIGHT = 0x2u;
|
||||
static constexpr auto SPEAKER_FRONT_CENTER = 0x4u;
|
||||
static constexpr auto SPEAKER_LOW_FREQUENCY = 0x8u;
|
||||
static constexpr auto SPEAKER_BACK_LEFT = 0x10u;
|
||||
static constexpr auto SPEAKER_BACK_RIGHT = 0x20u;
|
||||
static constexpr auto SPEAKER_FRONT_LEFT_OF_CENTER = 0x40u;
|
||||
static constexpr auto SPEAKER_FRONT_RIGHT_OF_CENTER = 0x80u;
|
||||
static constexpr auto SPEAKER_BACK_CENTER = 0x100u;
|
||||
static constexpr auto SPEAKER_SIDE_LEFT = 0x200u;
|
||||
static constexpr auto SPEAKER_SIDE_RIGHT = 0x400u;
|
||||
static constexpr auto SPEAKER_TOP_CENTER = 0x800u;
|
||||
static constexpr auto SPEAKER_TOP_FRONT_LEFT = 0x1000u;
|
||||
static constexpr auto SPEAKER_TOP_FRONT_CENTER = 0x2000u;
|
||||
static constexpr auto SPEAKER_TOP_FRONT_RIGHT = 0x4000u;
|
||||
static constexpr auto SPEAKER_TOP_BACK_LEFT = 0x8000u;
|
||||
static constexpr auto SPEAKER_TOP_BACK_CENTER = 0x10000u;
|
||||
static constexpr auto SPEAKER_TOP_BACK_RIGHT = 0x20000u;
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
|
||||
info.channelMap.clear();
|
||||
|
||||
const auto checkChannel = [channelMask, &info](auto bit, auto soundChannel)
|
||||
{
|
||||
if ((channelMask & bit) != 0)
|
||||
info.channelMap.push_back(soundChannel);
|
||||
};
|
||||
|
||||
checkChannel(SPEAKER_FRONT_LEFT, SoundChannel::FrontLeft);
|
||||
checkChannel(SPEAKER_FRONT_RIGHT, SoundChannel::FrontRight);
|
||||
checkChannel(SPEAKER_FRONT_CENTER, SoundChannel::FrontCenter);
|
||||
checkChannel(SPEAKER_LOW_FREQUENCY, SoundChannel::LowFrequencyEffects);
|
||||
checkChannel(SPEAKER_BACK_LEFT, SoundChannel::BackLeft);
|
||||
checkChannel(SPEAKER_BACK_RIGHT, SoundChannel::BackRight);
|
||||
checkChannel(SPEAKER_FRONT_LEFT_OF_CENTER, SoundChannel::FrontLeftOfCenter);
|
||||
checkChannel(SPEAKER_FRONT_RIGHT_OF_CENTER, SoundChannel::FrontRightOfCenter);
|
||||
checkChannel(SPEAKER_BACK_CENTER, SoundChannel::BackCenter);
|
||||
checkChannel(SPEAKER_SIDE_LEFT, SoundChannel::SideLeft);
|
||||
checkChannel(SPEAKER_SIDE_RIGHT, SoundChannel::SideRight);
|
||||
checkChannel(SPEAKER_TOP_CENTER, SoundChannel::TopCenter);
|
||||
checkChannel(SPEAKER_TOP_FRONT_LEFT, SoundChannel::TopFrontLeft);
|
||||
checkChannel(SPEAKER_TOP_FRONT_CENTER, SoundChannel::TopFrontCenter);
|
||||
checkChannel(SPEAKER_TOP_FRONT_RIGHT, SoundChannel::TopFrontRight);
|
||||
checkChannel(SPEAKER_TOP_BACK_LEFT, SoundChannel::TopBackLeft);
|
||||
checkChannel(SPEAKER_TOP_BACK_CENTER, SoundChannel::TopBackCenter);
|
||||
checkChannel(SPEAKER_TOP_BACK_RIGHT, SoundChannel::TopBackRight);
|
||||
|
||||
assert(info.channelCount == info.channelMap.size());
|
||||
|
||||
if (info.channelCount != info.channelMap.size())
|
||||
{
|
||||
err() << "WAV sound file channel count does not match number of set bits in channel mask" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Subformat
|
||||
char subformat[16];
|
||||
if (static_cast<std::size_t>(m_stream->read(subformat, static_cast<std::int64_t>(sizeof(subformat)))) !=
|
||||
sizeof(subformat))
|
||||
return std::nullopt;
|
||||
|
||||
if (std::memcmp(subformat, waveSubformatPcm, sizeof(subformat)) != 0)
|
||||
{
|
||||
err() << "Unsupported format: extensible format with non-PCM subformat" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (validBitsPerSample != bitsPerSample)
|
||||
{
|
||||
err() << "Unsupported format: sample size (" << validBitsPerSample
|
||||
<< " bits) and "
|
||||
"sample container size ("
|
||||
<< bitsPerSample << " bits) differ" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we don't have a waveFormatExtensible header, fill the channel map based on a best guess for mono/stereo
|
||||
info.channelMap.clear();
|
||||
|
||||
if (info.channelCount == 0)
|
||||
{
|
||||
err() << "WAV sound file channel count 0" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
else if (info.channelCount == 1)
|
||||
{
|
||||
info.channelMap.push_back(SoundChannel::Mono);
|
||||
}
|
||||
else if (info.channelCount == 2)
|
||||
{
|
||||
info.channelMap.push_back(SoundChannel::FrontLeft);
|
||||
info.channelMap.push_back(SoundChannel::FrontRight);
|
||||
}
|
||||
else
|
||||
{
|
||||
info.channelMap.push_back(SoundChannel::FrontLeft);
|
||||
info.channelMap.push_back(SoundChannel::FrontRight);
|
||||
|
||||
for (auto i = 2u; i < info.channelCount; ++i)
|
||||
info.channelMap.push_back(SoundChannel::Unspecified);
|
||||
}
|
||||
}
|
||||
|
||||
// Skip potential extra information
|
||||
if (m_stream->seek(subChunkStart + subChunkSize) == -1)
|
||||
return std::nullopt;
|
||||
}
|
||||
else if ((subChunkId[0] == 'd') && (subChunkId[1] == 'a') && (subChunkId[2] == 't') && (subChunkId[3] == 'a'))
|
||||
{
|
||||
// "data" chunk
|
||||
|
||||
// Compute the total number of samples
|
||||
info.sampleCount = subChunkSize / m_bytesPerSample;
|
||||
|
||||
// Store the start and end position of samples in the file
|
||||
m_dataStart = static_cast<std::uint64_t>(subChunkStart);
|
||||
m_dataEnd = m_dataStart + info.sampleCount * m_bytesPerSample;
|
||||
|
||||
dataChunkFound = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// unknown chunk, skip it
|
||||
if (m_stream->seek(m_stream->tell() + subChunkSize) == -1)
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
return framesRead * m_channelCount;
|
||||
}
|
||||
|
||||
} // namespace sf::priv
|
||||
|
@ -29,6 +29,8 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Audio/SoundFileReader.hpp>
|
||||
|
||||
#include <miniaudio.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <cstdint>
|
||||
@ -58,6 +60,12 @@ public:
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] static bool check(InputStream& stream);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Destructor
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
~SoundFileReaderWav() override;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Open a sound file for reading
|
||||
///
|
||||
@ -66,7 +74,7 @@ public:
|
||||
/// \return Properties of the loaded sound if the file was successfully opened
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
[[nodiscard]] std::optional<Info> open(sf::InputStream& stream) override;
|
||||
[[nodiscard]] std::optional<Info> open(InputStream& stream) override;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Change the current read position to the given sample offset
|
||||
@ -95,23 +103,11 @@ public:
|
||||
[[nodiscard]] std::uint64_t read(std::int16_t* samples, std::uint64_t maxCount) override;
|
||||
|
||||
private:
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Read the header of the open file
|
||||
///
|
||||
/// \param info Attributes of the sound file
|
||||
///
|
||||
/// \return True on success, false on error
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
std::optional<Info> parseHeader();
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
InputStream* m_stream{}; //!< Source stream to read from
|
||||
unsigned int m_bytesPerSample{}; //!< Size of a sample, in bytes
|
||||
std::uint64_t m_dataStart{}; //!< Starting position of the audio data in the open file
|
||||
std::uint64_t m_dataEnd{}; //!< Position one byte past the end of the audio data in the open file
|
||||
std::optional<ma_decoder> m_decoder; //!< wav decoder
|
||||
ma_uint32 m_channelCount{}; //!< Number of channels
|
||||
};
|
||||
|
||||
} // namespace sf::priv
|
||||
|
@ -31,6 +31,7 @@
|
||||
|
||||
#include <vorbis/vorbisenc.h>
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
@ -104,7 +105,7 @@ private:
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
unsigned int m_channelCount{}; //!< Channel count of the sound being written
|
||||
std::size_t m_remapTable[8]{}; //!< Table we use to remap source to target channel order
|
||||
std::array<std::size_t, 8> m_remapTable{}; //!< Table we use to remap source to target channel order
|
||||
std::ofstream m_file; //!< Output file
|
||||
ogg_stream_state m_ogg{}; //!< OGG stream
|
||||
vorbis_info m_vorbis{}; //!< Vorbis handle
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include <SFML/System/Utils.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <ostream>
|
||||
|
||||
#include <cassert>
|
||||
@ -45,25 +46,25 @@ namespace
|
||||
|
||||
void encode(std::ostream& stream, std::int16_t value)
|
||||
{
|
||||
const std::byte bytes[] = {static_cast<std::byte>(value & 0xFF), static_cast<std::byte>(value >> 8)};
|
||||
stream.write(reinterpret_cast<const char*>(bytes), sizeof(bytes));
|
||||
const std::array bytes = {static_cast<char>(value & 0xFF), static_cast<char>(value >> 8)};
|
||||
stream.write(bytes.data(), bytes.size());
|
||||
}
|
||||
|
||||
void encode(std::ostream& stream, std::uint16_t value)
|
||||
{
|
||||
const std::byte bytes[] = {static_cast<std::byte>(value & 0xFF), static_cast<std::byte>(value >> 8)};
|
||||
stream.write(reinterpret_cast<const char*>(bytes), sizeof(bytes));
|
||||
const std::array bytes = {static_cast<char>(value & 0xFF), static_cast<char>(value >> 8)};
|
||||
stream.write(bytes.data(), bytes.size());
|
||||
}
|
||||
|
||||
void encode(std::ostream& stream, std::uint32_t value)
|
||||
{
|
||||
const std::byte bytes[] = {
|
||||
static_cast<std::byte>(value & 0x000000FF),
|
||||
static_cast<std::byte>((value & 0x0000FF00) >> 8),
|
||||
static_cast<std::byte>((value & 0x00FF0000) >> 16),
|
||||
static_cast<std::byte>((value & 0xFF000000) >> 24),
|
||||
const std::array bytes = {
|
||||
static_cast<char>(value & 0x000000FF),
|
||||
static_cast<char>((value & 0x0000FF00) >> 8),
|
||||
static_cast<char>((value & 0x00FF0000) >> 16),
|
||||
static_cast<char>((value & 0xFF000000) >> 24),
|
||||
};
|
||||
stream.write(reinterpret_cast<const char*>(bytes), sizeof(bytes));
|
||||
stream.write(bytes.data(), bytes.size());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@ -247,17 +248,17 @@ void SoundFileWriterWav::writeHeader(unsigned int sampleRate, unsigned int chann
|
||||
assert(m_file.good() && "Most recent I/O operation failed");
|
||||
|
||||
// Write the main chunk ID
|
||||
char mainChunkId[4] = {'R', 'I', 'F', 'F'};
|
||||
m_file.write(mainChunkId, sizeof(mainChunkId));
|
||||
std::array mainChunkId = {'R', 'I', 'F', 'F'};
|
||||
m_file.write(mainChunkId.data(), mainChunkId.size());
|
||||
|
||||
// Write the main chunk header
|
||||
encode(m_file, static_cast<std::uint32_t>(0)); // 0 is a placeholder, will be written later
|
||||
char mainChunkFormat[4] = {'W', 'A', 'V', 'E'};
|
||||
m_file.write(mainChunkFormat, sizeof(mainChunkFormat));
|
||||
std::array mainChunkFormat = {'W', 'A', 'V', 'E'};
|
||||
m_file.write(mainChunkFormat.data(), mainChunkFormat.size());
|
||||
|
||||
// Write the sub-chunk 1 ("format") id and size
|
||||
char fmtChunkId[4] = {'f', 'm', 't', ' '};
|
||||
m_file.write(fmtChunkId, sizeof(fmtChunkId));
|
||||
std::array fmtChunkId = {'f', 'm', 't', ' '};
|
||||
m_file.write(fmtChunkId.data(), fmtChunkId.size());
|
||||
|
||||
if (channelCount > 2)
|
||||
{
|
||||
@ -295,14 +296,14 @@ void SoundFileWriterWav::writeHeader(unsigned int sampleRate, unsigned int chann
|
||||
encode(m_file, bitsPerSample);
|
||||
encode(m_file, channelMask);
|
||||
// Write the subformat (PCM)
|
||||
char subformat[16] =
|
||||
std::array subformat =
|
||||
{'\x01', '\x00', '\x00', '\x00', '\x00', '\x00', '\x10', '\x00', '\x80', '\x00', '\x00', '\xAA', '\x00', '\x38', '\x9B', '\x71'};
|
||||
m_file.write(subformat, sizeof(subformat));
|
||||
m_file.write(subformat.data(), subformat.size());
|
||||
}
|
||||
|
||||
// Write the sub-chunk 2 ("data") id and size
|
||||
char dataChunkId[4] = {'d', 'a', 't', 'a'};
|
||||
m_file.write(dataChunkId, sizeof(dataChunkId));
|
||||
std::array dataChunkId = {'d', 'a', 't', 'a'};
|
||||
m_file.write(dataChunkId.data(), dataChunkId.size());
|
||||
const std::uint32_t dataChunkSize = 0; // placeholder, will be written later
|
||||
encode(m_file, dataChunkSize);
|
||||
}
|
||||
|
@ -29,6 +29,7 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/Audio/SoundFileWriter.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
@ -107,7 +108,7 @@ private:
|
||||
////////////////////////////////////////////////////////////
|
||||
std::ofstream m_file; //!< File stream to write to
|
||||
unsigned int m_channelCount{}; //!< Channel count of the sound being written
|
||||
std::size_t m_remapTable[18]{}; //!< Table we use to remap source to target channel order
|
||||
std::array<std::size_t, 18> m_remapTable{}; //!< Table we use to remap source to target channel order
|
||||
};
|
||||
|
||||
} // namespace sf::priv
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include <miniaudio.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
|
||||
@ -189,14 +190,50 @@ SoundRecorder::SoundRecorder() : m_impl(std::make_unique<Impl>(this))
|
||||
|
||||
auto contextConfig = ma_context_config_init();
|
||||
contextConfig.pLog = &*m_impl->log;
|
||||
ma_uint32 deviceCount = 0;
|
||||
const auto nullBackend = ma_backend_null;
|
||||
const std::array<const ma_backend*, 2> backendLists{nullptr, &nullBackend};
|
||||
|
||||
if (const auto result = ma_context_init(nullptr, 0, &contextConfig, &*m_impl->context); result != MA_SUCCESS)
|
||||
for (const auto* backendList : backendLists)
|
||||
{
|
||||
// We can set backendCount to 1 since it is ignored when backends is set to nullptr
|
||||
if (const auto result = ma_context_init(backendList, 1, &contextConfig, &*m_impl->context); result != MA_SUCCESS)
|
||||
{
|
||||
m_impl->context.reset();
|
||||
err() << "Failed to initialize the audio context: " << ma_result_description(result) << std::endl;
|
||||
err() << "Failed to initialize the audio capture context: " << ma_result_description(result) << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Count the capture devices
|
||||
if (const auto result = ma_context_get_devices(&*m_impl->context, nullptr, nullptr, nullptr, &deviceCount);
|
||||
result != MA_SUCCESS)
|
||||
{
|
||||
err() << "Failed to get audio capture devices: " << ma_result_description(result) << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if there are audio capture devices available on the system
|
||||
if (deviceCount > 0)
|
||||
break;
|
||||
|
||||
// Warn if no devices were found using the default backend list
|
||||
if (backendList == nullptr)
|
||||
err() << "No audio capture devices available on the system" << std::endl;
|
||||
|
||||
// Clean up the context if we didn't find any devices
|
||||
ma_context_uninit(&*m_impl->context);
|
||||
}
|
||||
|
||||
// If the NULL audio backend also doesn't provide a device we give up
|
||||
if (deviceCount == 0)
|
||||
{
|
||||
m_impl->context.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_impl->context->backend == ma_backend_null)
|
||||
err() << "Using NULL audio backend for capture" << std::endl;
|
||||
|
||||
// Create the capture device
|
||||
m_impl->initialize();
|
||||
}
|
||||
|
@ -88,8 +88,8 @@ void SoundSource::setCone(const Cone& cone)
|
||||
{
|
||||
if (auto* sound = static_cast<ma_sound*>(getSound()))
|
||||
ma_sound_set_cone(sound,
|
||||
std::clamp(cone.innerAngle, sf::degrees(0), sf::degrees(360)).asRadians(),
|
||||
std::clamp(cone.outerAngle, sf::degrees(0), sf::degrees(360)).asRadians(),
|
||||
std::clamp(cone.innerAngle, Angle::Zero, degrees(360.f)).asRadians(),
|
||||
std::clamp(cone.outerAngle, Angle::Zero, degrees(360.f)).asRadians(),
|
||||
cone.outerGain);
|
||||
}
|
||||
|
||||
@ -166,6 +166,13 @@ void SoundSource::setAttenuation(float attenuation)
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// NOLINTNEXTLINE(performance-unnecessary-value-param)
|
||||
void SoundSource::setEffectProcessor(EffectProcessor)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
float SoundSource::getPitch() const
|
||||
{
|
||||
@ -241,12 +248,12 @@ SoundSource::Cone SoundSource::getCone() const
|
||||
float outerAngle = 0.f;
|
||||
Cone cone;
|
||||
ma_sound_get_cone(sound, &innerAngle, &outerAngle, &cone.outerGain);
|
||||
cone.innerAngle = sf::radians(innerAngle);
|
||||
cone.outerAngle = sf::radians(outerAngle);
|
||||
cone.innerAngle = radians(innerAngle);
|
||||
cone.outerAngle = radians(outerAngle);
|
||||
return cone;
|
||||
}
|
||||
|
||||
return Cone{sf::radians(0), sf::radians(0), 0.f};
|
||||
return Cone{radians(0), radians(0), 0.f};
|
||||
}
|
||||
|
||||
|
||||
|
@ -55,6 +55,7 @@ struct SoundStream::Impl
|
||||
~Impl()
|
||||
{
|
||||
ma_sound_uninit(&sound);
|
||||
ma_node_uninit(&effectNode, nullptr);
|
||||
ma_data_source_uninit(&dataSourceBase);
|
||||
}
|
||||
|
||||
@ -91,6 +92,34 @@ struct SoundStream::Impl
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the custom effect node
|
||||
effectNodeVTable.onProcess =
|
||||
[](ma_node* node, const float** framesIn, ma_uint32* frameCountIn, float** framesOut, ma_uint32* frameCountOut)
|
||||
{ static_cast<EffectNode*>(node)->impl->processEffect(framesIn, *frameCountIn, framesOut, *frameCountOut); };
|
||||
effectNodeVTable.onGetRequiredInputFrameCount = nullptr;
|
||||
effectNodeVTable.inputBusCount = 1;
|
||||
effectNodeVTable.outputBusCount = 1;
|
||||
effectNodeVTable.flags = MA_NODE_FLAG_CONTINUOUS_PROCESSING | MA_NODE_FLAG_ALLOW_NULL_INPUT;
|
||||
|
||||
const auto nodeChannelCount = ma_engine_get_channels(engine);
|
||||
ma_node_config nodeConfig = ma_node_config_init();
|
||||
nodeConfig.vtable = &effectNodeVTable;
|
||||
nodeConfig.pInputChannels = &nodeChannelCount;
|
||||
nodeConfig.pOutputChannels = &nodeChannelCount;
|
||||
|
||||
if (const ma_result result = ma_node_init(ma_engine_get_node_graph(engine), &nodeConfig, nullptr, &effectNode);
|
||||
result != MA_SUCCESS)
|
||||
{
|
||||
err() << "Failed to initialize effect node: " << ma_result_description(result) << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
effectNode.impl = this;
|
||||
effectNode.channelCount = nodeChannelCount;
|
||||
|
||||
// Route the sound through the effect node depending on whether an effect processor is set
|
||||
connectEffect(bool{effectProcessor});
|
||||
|
||||
// Because we are providing a custom data source, we have to provide the channel map ourselves
|
||||
if (!channelMap.empty())
|
||||
{
|
||||
@ -111,7 +140,79 @@ struct SoundStream::Impl
|
||||
|
||||
void reinitialize()
|
||||
{
|
||||
priv::MiniaudioUtils::reinitializeSound(sound, [this] { initialize(); });
|
||||
priv::MiniaudioUtils::reinitializeSound(sound,
|
||||
[this]
|
||||
{
|
||||
ma_node_uninit(&effectNode, nullptr);
|
||||
initialize();
|
||||
});
|
||||
}
|
||||
|
||||
void processEffect(const float** framesIn, ma_uint32& frameCountIn, float** framesOut, ma_uint32& frameCountOut) const
|
||||
{
|
||||
// If a processor is set, call it
|
||||
if (effectProcessor)
|
||||
{
|
||||
if (!framesIn)
|
||||
frameCountIn = 0;
|
||||
|
||||
effectProcessor(framesIn ? framesIn[0] : nullptr, frameCountIn, framesOut[0], frameCountOut, effectNode.channelCount);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise just pass the data through 1:1
|
||||
if (framesIn == nullptr)
|
||||
{
|
||||
frameCountIn = 0;
|
||||
frameCountOut = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto toProcess = std::min(frameCountIn, frameCountOut);
|
||||
std::memcpy(framesOut[0], framesIn[0], toProcess * effectNode.channelCount * sizeof(float));
|
||||
frameCountIn = toProcess;
|
||||
frameCountOut = toProcess;
|
||||
}
|
||||
|
||||
void connectEffect(bool connect)
|
||||
{
|
||||
auto* engine = priv::AudioDevice::get().getEngine();
|
||||
|
||||
if (engine == nullptr)
|
||||
{
|
||||
err() << "Failed to connect effect: No engine available" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (connect)
|
||||
{
|
||||
// Attach the custom effect node output to our engine endpoint
|
||||
if (const ma_result result = ma_node_attach_output_bus(&effectNode, 0, ma_engine_get_endpoint(engine), 0);
|
||||
result != MA_SUCCESS)
|
||||
{
|
||||
err() << "Failed to attach effect node output to endpoint: " << ma_result_description(result) << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Detach the custom effect node output from our engine endpoint
|
||||
if (const ma_result result = ma_node_detach_output_bus(&effectNode, 0); result != MA_SUCCESS)
|
||||
{
|
||||
err() << "Failed to detach effect node output from endpoint: " << ma_result_description(result)
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Attach the sound output to the custom effect node or the engine endpoint
|
||||
if (const ma_result
|
||||
result = ma_node_attach_output_bus(&sound, 0, connect ? &effectNode : ma_engine_get_endpoint(engine), 0);
|
||||
result != MA_SUCCESS)
|
||||
{
|
||||
err() << "Failed to attach sound node output to effect node: " << ma_result_description(result) << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static ma_result read(ma_data_source* dataSource, void* framesOut, ma_uint64 frameCount, ma_uint64* framesRead)
|
||||
@ -238,8 +339,17 @@ struct SoundStream::Impl
|
||||
////////////////////////////////////////////////////////////
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
struct EffectNode
|
||||
{
|
||||
ma_node_base base{};
|
||||
Impl* impl{};
|
||||
ma_uint32 channelCount{};
|
||||
};
|
||||
|
||||
ma_data_source_base dataSourceBase{}; //!< The struct that makes this object a miniaudio data source (must be first member)
|
||||
SoundStream* const owner; //!< Owning SoundStream object
|
||||
ma_node_vtable effectNodeVTable{}; //!< Vtable of the effect node
|
||||
EffectNode effectNode; //!< The engine node that performs effect processing
|
||||
std::vector<ma_channel> soundChannelMap; //!< The map of position in sample frame to sound channel (miniaudio channels)
|
||||
ma_sound sound{}; //!< The sound
|
||||
std::vector<std::int16_t> sampleBuffer; //!< Our temporary sample buffer
|
||||
@ -251,6 +361,7 @@ struct SoundStream::Impl
|
||||
bool loop{}; //!< Loop flag (true to loop, false to play once)
|
||||
bool streaming{true}; //!< True if we are still streaming samples from the source
|
||||
Status status{Status::Stopped}; //!< The status
|
||||
EffectProcessor effectProcessor; //!< The effect processor
|
||||
};
|
||||
|
||||
|
||||
@ -395,6 +506,14 @@ bool SoundStream::getLoop() const
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void SoundStream::setEffectProcessor(EffectProcessor effectProcessor)
|
||||
{
|
||||
m_impl->effectProcessor = std::move(effectProcessor);
|
||||
m_impl->connectEffect(bool{m_impl->effectProcessor});
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
std::optional<std::uint64_t> SoundStream::onLoop()
|
||||
{
|
||||
|
@ -71,7 +71,7 @@ std::size_t CircleShape::getPointCount() const
|
||||
////////////////////////////////////////////////////////////
|
||||
Vector2f CircleShape::getPoint(std::size_t index) const
|
||||
{
|
||||
const Angle angle = static_cast<float>(index) / static_cast<float>(m_pointCount) * sf::degrees(360) - sf::degrees(90);
|
||||
const Angle angle = static_cast<float>(index) / static_cast<float>(m_pointCount) * degrees(360.f) - degrees(90.f);
|
||||
return Vector2f(m_radius, m_radius) + Vector2f(m_radius, angle);
|
||||
}
|
||||
|
||||
|
@ -784,7 +784,7 @@ bool Font::setCurrentSize(unsigned int characterSize) const
|
||||
Font::Page::Page(bool smooth)
|
||||
{
|
||||
// Make sure that the texture is initialized by default
|
||||
sf::Image image;
|
||||
Image image;
|
||||
image.create({128, 128}, Color::Transparent);
|
||||
|
||||
// Reserve a 2x2 white square for texturing underlines
|
||||
|
@ -61,9 +61,9 @@ void ensureExtensionsInit()
|
||||
initialized = true;
|
||||
|
||||
#ifdef SFML_OPENGL_ES
|
||||
gladLoadGLES1(sf::Context::getFunction);
|
||||
gladLoadGLES1(Context::getFunction);
|
||||
#else
|
||||
gladLoadGL(sf::Context::getFunction);
|
||||
gladLoadGL(Context::getFunction);
|
||||
#endif
|
||||
|
||||
// Retrieve the context version number
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include <SFML/System/InputStream.hpp>
|
||||
#include <SFML/System/Utils.hpp>
|
||||
#ifdef SFML_SYSTEM_ANDROID
|
||||
#include <SFML/System/Android/Activity.hpp>
|
||||
#include <SFML/System/Android/ResourceStream.hpp>
|
||||
#endif
|
||||
|
||||
@ -157,7 +158,15 @@ void Image::create(const Vector2u& size, const std::uint8_t* pixels)
|
||||
////////////////////////////////////////////////////////////
|
||||
bool Image::loadFromFile(const std::filesystem::path& filename)
|
||||
{
|
||||
#ifndef SFML_SYSTEM_ANDROID
|
||||
#ifdef SFML_SYSTEM_ANDROID
|
||||
|
||||
if (priv::getActivityStatesPtr() != nullptr)
|
||||
{
|
||||
priv::ResourceStream stream(filename);
|
||||
return loadFromStream(stream);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Clear the array (just in case)
|
||||
m_pixels.clear();
|
||||
@ -186,13 +195,6 @@ bool Image::loadFromFile(const std::filesystem::path& filename)
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
priv::ResourceStream stream(filename);
|
||||
return loadFromStream(stream);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
@ -740,7 +740,7 @@ bool Shader::isAvailable()
|
||||
const TransientContextLock contextLock;
|
||||
|
||||
// Make sure that extensions are initialized
|
||||
sf::priv::ensureExtensionsInit();
|
||||
priv::ensureExtensionsInit();
|
||||
|
||||
return GLEXT_multitexture && GLEXT_shading_language_100 && GLEXT_shader_objects && GLEXT_vertex_shader &&
|
||||
GLEXT_fragment_shader;
|
||||
@ -758,7 +758,7 @@ bool Shader::isGeometryAvailable()
|
||||
const TransientContextLock contextLock;
|
||||
|
||||
// Make sure that extensions are initialized
|
||||
sf::priv::ensureExtensionsInit();
|
||||
priv::ensureExtensionsInit();
|
||||
|
||||
return isAvailable() && (GLEXT_geometry_shader4 || GLEXT_GL_VERSION_3_2);
|
||||
}();
|
||||
|
@ -381,7 +381,7 @@ void Text::ensureGeometryUpdate() const
|
||||
const bool isBold = m_style & Bold;
|
||||
const bool isUnderlined = m_style & Underlined;
|
||||
const bool isStrikeThrough = m_style & StrikeThrough;
|
||||
const float italicShear = (m_style & Italic) ? sf::degrees(12).asRadians() : 0.f;
|
||||
const float italicShear = (m_style & Italic) ? degrees(12).asRadians() : 0.f;
|
||||
const float underlineOffset = m_font->getUnderlinePosition(m_characterSize);
|
||||
const float underlineThickness = m_font->getUnderlineThickness(m_characterSize);
|
||||
|
||||
|
@ -37,6 +37,7 @@
|
||||
#include <SFML/System/Err.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <ostream>
|
||||
#include <utility>
|
||||
@ -861,7 +862,7 @@ void Texture::bind(const Texture* texture, CoordinateType coordinateType)
|
||||
if ((coordinateType == CoordinateType::Pixels) || texture->m_pixelsFlipped)
|
||||
{
|
||||
// clang-format off
|
||||
GLfloat matrix[16] = {1.f, 0.f, 0.f, 0.f,
|
||||
std::array matrix = {1.f, 0.f, 0.f, 0.f,
|
||||
0.f, 1.f, 0.f, 0.f,
|
||||
0.f, 0.f, 1.f, 0.f,
|
||||
0.f, 0.f, 0.f, 1.f};
|
||||
@ -884,7 +885,7 @@ void Texture::bind(const Texture* texture, CoordinateType coordinateType)
|
||||
|
||||
// Load the matrix
|
||||
glCheck(glMatrixMode(GL_TEXTURE));
|
||||
glCheck(glLoadMatrixf(matrix));
|
||||
glCheck(glLoadMatrixf(matrix.data()));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -921,7 +922,7 @@ unsigned int Texture::getMaximumSize()
|
||||
GLint value = 0;
|
||||
|
||||
// Make sure that extensions are initialized
|
||||
sf::priv::ensureExtensionsInit();
|
||||
priv::ensureExtensionsInit();
|
||||
|
||||
glCheck(glGetIntegerv(GL_MAX_TEXTURE_SIZE, &value));
|
||||
|
||||
|
@ -31,6 +31,8 @@
|
||||
#include <SFML/System/String.hpp>
|
||||
#include <SFML/System/Utils.hpp>
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <cstring>
|
||||
#include <cwchar>
|
||||
|
||||
@ -212,8 +214,8 @@ Packet& Packet::operator>>(std::uint64_t& data)
|
||||
{
|
||||
// Since ntohll is not available everywhere, we have to convert
|
||||
// to network byte order (big endian) manually
|
||||
std::byte bytes[sizeof(data)];
|
||||
std::memcpy(bytes, &m_data[m_readPos], sizeof(data));
|
||||
std::array<std::byte, sizeof(data)> bytes{};
|
||||
std::memcpy(bytes.data(), &m_data[m_readPos], sizeof(data));
|
||||
|
||||
data = toInteger<std::uint64_t>(bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]);
|
||||
|
||||
@ -427,7 +429,7 @@ Packet& Packet::operator<<(std::int64_t data)
|
||||
// Since htonll is not available everywhere, we have to convert
|
||||
// to network byte order (big endian) manually
|
||||
|
||||
std::uint8_t toWrite[] = {static_cast<std::uint8_t>((data >> 56) & 0xFF),
|
||||
std::array toWrite = {static_cast<std::uint8_t>((data >> 56) & 0xFF),
|
||||
static_cast<std::uint8_t>((data >> 48) & 0xFF),
|
||||
static_cast<std::uint8_t>((data >> 40) & 0xFF),
|
||||
static_cast<std::uint8_t>((data >> 32) & 0xFF),
|
||||
@ -447,7 +449,7 @@ Packet& Packet::operator<<(std::uint64_t data)
|
||||
// Since htonll is not available everywhere, we have to convert
|
||||
// to network byte order (big endian) manually
|
||||
|
||||
std::uint8_t toWrite[] = {static_cast<std::uint8_t>((data >> 56) & 0xFF),
|
||||
std::array toWrite = {static_cast<std::uint8_t>((data >> 56) & 0xFF),
|
||||
static_cast<std::uint8_t>((data >> 48) & 0xFF),
|
||||
static_cast<std::uint8_t>((data >> 40) & 0xFF),
|
||||
static_cast<std::uint8_t>((data >> 32) & 0xFF),
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include <SFML/System/Err.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <ostream>
|
||||
|
||||
#include <cstring>
|
||||
@ -401,12 +402,12 @@ Socket::Status TcpSocket::receive(Packet& packet)
|
||||
}
|
||||
|
||||
// Loop until we receive all the packet data
|
||||
char buffer[1024];
|
||||
std::array<char, 1024> buffer{};
|
||||
while (m_pendingPacket.data.size() < packetSize)
|
||||
{
|
||||
// Receive a chunk of data
|
||||
const std::size_t sizeToGet = std::min(packetSize - m_pendingPacket.data.size(), sizeof(buffer));
|
||||
const Status status = receive(buffer, sizeToGet, received);
|
||||
const Status status = receive(buffer.data(), sizeToGet, received);
|
||||
if (status != Status::Done)
|
||||
return status;
|
||||
|
||||
@ -415,7 +416,7 @@ Socket::Status TcpSocket::receive(Packet& packet)
|
||||
{
|
||||
m_pendingPacket.data.resize(m_pendingPacket.data.size() + received);
|
||||
std::byte* begin = m_pendingPacket.data.data() + m_pendingPacket.data.size() - received;
|
||||
std::memcpy(begin, buffer, received);
|
||||
std::memcpy(begin, buffer.data(), received);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,7 @@
|
||||
#include <SFML/System/Vector2.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
|
@ -27,6 +27,7 @@
|
||||
////////////////////////////////////////////////////////////
|
||||
#include <SFML/System/FileInputStream.hpp>
|
||||
#ifdef SFML_SYSTEM_ANDROID
|
||||
#include <SFML/System/Android/Activity.hpp>
|
||||
#include <SFML/System/Android/ResourceStream.hpp>
|
||||
#endif
|
||||
#include <memory>
|
||||
@ -36,12 +37,10 @@
|
||||
namespace sf
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
#ifndef SFML_SYSTEM_ANDROID
|
||||
void FileInputStream::FileCloser::operator()(std::FILE* file)
|
||||
{
|
||||
std::fclose(file);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
@ -64,69 +63,88 @@ FileInputStream& FileInputStream::operator=(FileInputStream&&) noexcept = defaul
|
||||
bool FileInputStream::open(const std::filesystem::path& filename)
|
||||
{
|
||||
#ifdef SFML_SYSTEM_ANDROID
|
||||
m_file = std::make_unique<priv::ResourceStream>(filename);
|
||||
return m_file->tell() != -1;
|
||||
#else
|
||||
if (priv::getActivityStatesPtr() != nullptr)
|
||||
{
|
||||
m_androidFile = std::make_unique<priv::ResourceStream>(filename);
|
||||
return m_androidFile->tell() != -1;
|
||||
}
|
||||
#endif
|
||||
#ifdef SFML_SYSTEM_WINDOWS
|
||||
m_file.reset(_wfopen(filename.c_str(), L"rb"));
|
||||
#else
|
||||
m_file.reset(std::fopen(filename.c_str(), "rb"));
|
||||
#endif
|
||||
return m_file != nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
std::int64_t FileInputStream::read(void* data, std::int64_t size)
|
||||
{
|
||||
#ifdef SFML_SYSTEM_ANDROID
|
||||
if (priv::getActivityStatesPtr() != nullptr)
|
||||
{
|
||||
if (!m_androidFile)
|
||||
return -1;
|
||||
return m_androidFile->read(data, size);
|
||||
}
|
||||
#endif
|
||||
if (!m_file)
|
||||
return -1;
|
||||
#ifdef SFML_SYSTEM_ANDROID
|
||||
return m_file->read(data, size);
|
||||
#else
|
||||
return static_cast<std::int64_t>(std::fread(data, 1, static_cast<std::size_t>(size), m_file.get()));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
std::int64_t FileInputStream::seek(std::int64_t position)
|
||||
{
|
||||
#ifdef SFML_SYSTEM_ANDROID
|
||||
if (priv::getActivityStatesPtr() != nullptr)
|
||||
{
|
||||
if (!m_androidFile)
|
||||
return -1;
|
||||
return m_androidFile->seek(position);
|
||||
}
|
||||
#endif
|
||||
if (!m_file)
|
||||
return -1;
|
||||
#ifdef SFML_SYSTEM_ANDROID
|
||||
return m_file->seek(position);
|
||||
#else
|
||||
if (std::fseek(m_file.get(), static_cast<long>(position), SEEK_SET))
|
||||
return -1;
|
||||
|
||||
return tell();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
std::int64_t FileInputStream::tell()
|
||||
{
|
||||
#ifdef SFML_SYSTEM_ANDROID
|
||||
if (priv::getActivityStatesPtr() != nullptr)
|
||||
{
|
||||
if (!m_androidFile)
|
||||
return -1;
|
||||
return m_androidFile->tell();
|
||||
}
|
||||
#endif
|
||||
if (!m_file)
|
||||
return -1;
|
||||
#ifdef SFML_SYSTEM_ANDROID
|
||||
return m_file->tell();
|
||||
#else
|
||||
return std::ftell(m_file.get());
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
std::int64_t FileInputStream::getSize()
|
||||
{
|
||||
#ifdef SFML_SYSTEM_ANDROID
|
||||
if (priv::getActivityStatesPtr() != nullptr)
|
||||
{
|
||||
if (!m_androidFile)
|
||||
return -1;
|
||||
return m_androidFile->getSize();
|
||||
}
|
||||
#endif
|
||||
if (!m_file)
|
||||
return -1;
|
||||
#ifdef SFML_SYSTEM_ANDROID
|
||||
return m_file->getSize();
|
||||
#else
|
||||
const std::int64_t position = tell();
|
||||
std::fseek(m_file.get(), 0, SEEK_END);
|
||||
const std::int64_t size = tell();
|
||||
@ -135,7 +153,6 @@ std::int64_t FileInputStream::getSize()
|
||||
return -1;
|
||||
|
||||
return size;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace sf
|
||||
|
@ -264,10 +264,10 @@ std::wstring String::toWideString() const
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
sf::U8String String::toUtf8() const
|
||||
U8String String::toUtf8() const
|
||||
{
|
||||
// Prepare the output string
|
||||
sf::U8String output;
|
||||
U8String output;
|
||||
output.reserve(m_string.length());
|
||||
|
||||
// Convert
|
||||
|
@ -38,7 +38,7 @@ namespace sf::priv
|
||||
////////////////////////////////////////////////////////////
|
||||
String ClipboardImpl::getString()
|
||||
{
|
||||
sf::err() << "Clipboard API not implemented for Android.\n";
|
||||
err() << "Clipboard API not implemented for Android.\n";
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ String ClipboardImpl::getString()
|
||||
////////////////////////////////////////////////////////////
|
||||
void ClipboardImpl::setString(const String& /* text */)
|
||||
{
|
||||
sf::err() << "Clipboard API not implemented for Android.\n";
|
||||
err() << "Clipboard API not implemented for Android.\n";
|
||||
}
|
||||
|
||||
} // namespace sf::priv
|
||||
|
@ -55,7 +55,7 @@ struct JoystickState
|
||||
{
|
||||
bool connected{}; //!< Is the joystick currently connected?
|
||||
EnumArray<Joystick::Axis, float, Joystick::AxisCount> axes{}; //!< Position of each axis, in range [-100, 100]
|
||||
bool buttons[Joystick::ButtonCount]{}; //!< Status of each button (true = pressed)
|
||||
std::array<bool, Joystick::ButtonCount> buttons{}; //!< Status of each button (true = pressed)
|
||||
};
|
||||
|
||||
} // namespace sf::priv
|
||||
|
@ -30,6 +30,8 @@
|
||||
#include <SFML/Window/Joystick.hpp>
|
||||
#include <SFML/Window/JoystickImpl.hpp>
|
||||
|
||||
#include <array>
|
||||
|
||||
|
||||
namespace sf::priv
|
||||
{
|
||||
@ -124,7 +126,7 @@ private:
|
||||
////////////////////////////////////////////////////////////
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
Item m_joysticks[Joystick::Count]; //!< Joysticks information and state
|
||||
std::array<Item, Joystick::Count> m_joysticks; //!< Joysticks information and state
|
||||
};
|
||||
|
||||
} // namespace sf::priv
|
||||
|
@ -33,6 +33,7 @@
|
||||
|
||||
#include <SFML/System/Err.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include <ostream>
|
||||
#include <vector>
|
||||
@ -513,10 +514,10 @@ void GlxContext::createSurface(GlxContext* shared, const Vector2u& size, unsigne
|
||||
|
||||
if (config)
|
||||
{
|
||||
int attributes[] =
|
||||
std::array attributes =
|
||||
{GLX_PBUFFER_WIDTH, static_cast<int>(size.x), GLX_PBUFFER_HEIGHT, static_cast<int>(size.y), 0, 0};
|
||||
|
||||
m_pbuffer = glXCreatePbuffer(m_display.get(), *config, attributes);
|
||||
m_pbuffer = glXCreatePbuffer(m_display.get(), *config, attributes.data());
|
||||
|
||||
updateSettingsFromVisualInfo(&visualInfo);
|
||||
|
||||
@ -578,11 +579,11 @@ void GlxContext::createContext(GlxContext* shared)
|
||||
|
||||
glXQueryDrawable(m_display.get(), m_pbuffer, GLX_FBCONFIG_ID, &fbConfigId);
|
||||
|
||||
int attributes[] = {GLX_FBCONFIG_ID, static_cast<int>(fbConfigId), 0, 0};
|
||||
std::array attributes = {GLX_FBCONFIG_ID, static_cast<int>(fbConfigId), 0, 0};
|
||||
|
||||
int count = 0;
|
||||
const auto fbconfig = X11Ptr<GLXFBConfig>(
|
||||
glXChooseFBConfig(m_display.get(), DefaultScreen(m_display.get()), attributes, &count));
|
||||
glXChooseFBConfig(m_display.get(), DefaultScreen(m_display.get()), attributes.data(), &count));
|
||||
|
||||
if (count == 1)
|
||||
visualInfo = X11Ptr<XVisualInfo>(glXGetVisualFromFBConfig(m_display.get(), *fbconfig));
|
||||
|
@ -400,14 +400,13 @@ std::string getJoystickName(unsigned int index)
|
||||
if (fd >= 0)
|
||||
{
|
||||
// Get the name
|
||||
char name[128] = {};
|
||||
|
||||
const int result = ioctl(fd, JSIOCGNAME(sizeof(name)), name);
|
||||
std::array<char, 128> name{};
|
||||
const int result = ioctl(fd, JSIOCGNAME(name.size()), name.data());
|
||||
|
||||
::close(fd);
|
||||
|
||||
if (result >= 0)
|
||||
return name;
|
||||
return name.data();
|
||||
}
|
||||
|
||||
// Fall back to manual USB chain walk via udev
|
||||
@ -600,7 +599,7 @@ JoystickCaps JoystickImpl::getCapabilities() const
|
||||
ioctl(m_file, JSIOCGAXES, &axesCount);
|
||||
for (int i = 0; i < axesCount; ++i)
|
||||
{
|
||||
switch (m_mapping[i])
|
||||
switch (m_mapping[static_cast<std::size_t>(i)])
|
||||
{
|
||||
// clang-format off
|
||||
case ABS_X: caps.axes[Joystick::Axis::X] = true; break;
|
||||
@ -650,7 +649,7 @@ JoystickState JoystickImpl::JoystickImpl::update()
|
||||
{
|
||||
const float value = joyState.value * 100.f / 32767.f;
|
||||
|
||||
if (joyState.number < ABS_MAX + 1)
|
||||
if (joyState.number < m_mapping.size())
|
||||
{
|
||||
switch (m_mapping[joyState.number])
|
||||
{
|
||||
|
@ -106,9 +106,9 @@ private:
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
int m_file{-1}; ///< File descriptor of the joystick
|
||||
char m_mapping[ABS_MAX + 1]{0}; ///< Axes mapping (index to axis id)
|
||||
std::array<char, ABS_CNT> m_mapping{}; ///< Axes mapping (index to axis id)
|
||||
JoystickState m_state; ///< Current state of the joystick
|
||||
sf::Joystick::Identification m_identification; ///< Identification of the joystick
|
||||
Joystick::Identification m_identification; ///< Identification of the joystick
|
||||
};
|
||||
|
||||
} // namespace sf::priv
|
||||
|
@ -138,7 +138,7 @@ bool WindowBase::isOpen() const
|
||||
////////////////////////////////////////////////////////////
|
||||
Event WindowBase::pollEvent()
|
||||
{
|
||||
sf::Event event;
|
||||
Event event;
|
||||
if (m_impl && (event = m_impl->popEvent(false)))
|
||||
filterEvent(event);
|
||||
return event;
|
||||
@ -148,7 +148,7 @@ Event WindowBase::pollEvent()
|
||||
////////////////////////////////////////////////////////////
|
||||
Event WindowBase::waitEvent()
|
||||
{
|
||||
sf::Event event;
|
||||
Event event;
|
||||
if (m_impl && (event = m_impl->popEvent(true)))
|
||||
filterEvent(event);
|
||||
return event;
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include <SFML/System/Sleep.hpp>
|
||||
#include <SFML/System/Time.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#include <cmath>
|
||||
@ -96,7 +97,7 @@ namespace sf::priv
|
||||
////////////////////////////////////////////////////////////
|
||||
struct WindowImpl::JoystickStatesImpl
|
||||
{
|
||||
JoystickState states[Joystick::Count]{}; //!< Previous state of the joysticks
|
||||
std::array<JoystickState, Joystick::Count> states{}; //!< Previous state of the joysticks
|
||||
};
|
||||
|
||||
|
||||
@ -131,7 +132,7 @@ WindowImpl::WindowImpl() : m_joystickStatesImpl(std::make_unique<JoystickStatesI
|
||||
}
|
||||
|
||||
// Get the initial sensor states
|
||||
for (sf::Vector3f& vec : m_sensorValue)
|
||||
for (Vector3f& vec : m_sensorValue)
|
||||
vec = Vector3f(0, 0, 0);
|
||||
}
|
||||
|
||||
@ -202,7 +203,7 @@ Event WindowImpl::popEvent(bool block)
|
||||
}
|
||||
}
|
||||
|
||||
sf::Event event;
|
||||
Event event;
|
||||
|
||||
// Pop the first event of the queue, if it is not empty
|
||||
if (!m_events.empty())
|
||||
|
@ -34,11 +34,6 @@
|
||||
namespace
|
||||
{
|
||||
unsigned int deviceMotionEnabledCount = 0;
|
||||
|
||||
float toDegrees(float radians)
|
||||
{
|
||||
return sf::radians(radians).asDegrees();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -145,10 +140,10 @@ Vector3f SensorImpl::update()
|
||||
break;
|
||||
|
||||
case Sensor::Type::Gyroscope:
|
||||
// Rotation rates are given in rad/s, convert to deg/s
|
||||
value.x = toDegrees(static_cast<float>(manager.gyroData.rotationRate.x));
|
||||
value.y = toDegrees(static_cast<float>(manager.gyroData.rotationRate.y));
|
||||
value.z = toDegrees(static_cast<float>(manager.gyroData.rotationRate.z));
|
||||
// Rotation rates are given in rad/s
|
||||
value.x = static_cast<float>(manager.gyroData.rotationRate.x);
|
||||
value.y = static_cast<float>(manager.gyroData.rotationRate.y);
|
||||
value.z = static_cast<float>(manager.gyroData.rotationRate.z);
|
||||
break;
|
||||
|
||||
case Sensor::Type::Magnetometer:
|
||||
@ -166,10 +161,10 @@ Vector3f SensorImpl::update()
|
||||
break;
|
||||
|
||||
case Sensor::Type::Orientation:
|
||||
// Absolute rotation (Euler) angles are given in radians, convert to degrees
|
||||
value.x = toDegrees(static_cast<float>(manager.deviceMotion.attitude.yaw));
|
||||
value.y = toDegrees(static_cast<float>(manager.deviceMotion.attitude.pitch));
|
||||
value.z = toDegrees(static_cast<float>(manager.deviceMotion.attitude.roll));
|
||||
// Absolute rotation (Euler) angles are given in radians
|
||||
value.x = static_cast<float>(manager.deviceMotion.attitude.yaw);
|
||||
value.y = static_cast<float>(manager.deviceMotion.attitude.pitch);
|
||||
value.z = static_cast<float>(manager.deviceMotion.attitude.roll);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -29,6 +29,8 @@
|
||||
#include <SFML/Window/macOS/HIDInputManager.hpp>
|
||||
#include <SFML/Window/macOS/HIDJoystickManager.hpp>
|
||||
|
||||
#include <array>
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Private data
|
||||
////////////////////////////////////////////////////////////
|
||||
@ -75,11 +77,8 @@ HIDJoystickManager::HIDJoystickManager()
|
||||
|
||||
CFDictionaryRef mask1 = HIDInputManager::copyDevicesMask(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad);
|
||||
|
||||
CFDictionaryRef maskArray[2];
|
||||
maskArray[0] = mask0;
|
||||
maskArray[1] = mask1;
|
||||
|
||||
CFArrayRef mask = CFArrayCreate(nullptr, reinterpret_cast<const void**>(maskArray), 2, nullptr);
|
||||
std::array maskArray = {mask0, mask1};
|
||||
CFArrayRef mask = CFArrayCreate(nullptr, reinterpret_cast<const void**>(maskArray.data()), maskArray.size(), nullptr);
|
||||
|
||||
IOHIDManagerSetDeviceMatchingMultiple(m_manager, mask);
|
||||
CFRelease(mask);
|
||||
|
@ -111,7 +111,7 @@ private:
|
||||
// Member data
|
||||
////////////////////////////////////////////////////////////
|
||||
using Location = long;
|
||||
using AxisMap = std::unordered_map<sf::Joystick::Axis, IOHIDElementRef>;
|
||||
using AxisMap = std::unordered_map<Joystick::Axis, IOHIDElementRef>;
|
||||
using ButtonsVector = std::vector<IOHIDElementRef>;
|
||||
|
||||
AxisMap m_axis; ///< Axes (but not POV/Hat) of the joystick
|
||||
@ -121,7 +121,7 @@ private:
|
||||
Joystick::Identification m_identification; ///< Joystick identification
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
static inline Location m_locationIDs[sf::Joystick::Count]{}; ///< Global Joystick register
|
||||
static inline std::array<Location, Joystick::Count> m_locationIDs{}; ///< Global Joystick register
|
||||
/// For a corresponding SFML index, m_locationIDs is either some USB
|
||||
/// location or 0 if there isn't currently a connected joystick device
|
||||
};
|
||||
|
@ -229,7 +229,7 @@ void SFContext::createContext(SFContext* shared, unsigned int bitsPerPixel, cons
|
||||
{
|
||||
if (!(m_settings.attributeFlags & ContextSettings::Core))
|
||||
{
|
||||
sf::err() << "Warning. Compatibility profile not supported on this platform." << std::endl;
|
||||
err() << "Warning. Compatibility profile not supported on this platform." << std::endl;
|
||||
m_settings.attributeFlags |= ContextSettings::Core;
|
||||
}
|
||||
m_settings.majorVersion = 3;
|
||||
@ -240,7 +240,7 @@ void SFContext::createContext(SFContext* shared, unsigned int bitsPerPixel, cons
|
||||
|
||||
if (m_settings.attributeFlags & ContextSettings::Debug)
|
||||
{
|
||||
sf::err() << "Warning. OpenGL debugging not supported on this platform." << std::endl;
|
||||
err() << "Warning. OpenGL debugging not supported on this platform." << std::endl;
|
||||
m_settings.attributeFlags &= ~static_cast<unsigned int>(ContextSettings::Debug);
|
||||
}
|
||||
|
||||
@ -254,7 +254,7 @@ void SFContext::createContext(SFContext* shared, unsigned int bitsPerPixel, cons
|
||||
|
||||
if (pixFmt == nil)
|
||||
{
|
||||
sf::err() << "Error. Unable to find a suitable pixel format." << std::endl;
|
||||
err() << "Error. Unable to find a suitable pixel format." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -267,7 +267,7 @@ void SFContext::createContext(SFContext* shared, unsigned int bitsPerPixel, cons
|
||||
|
||||
if (sharedContext == [NSOpenGLContext currentContext])
|
||||
{
|
||||
sf::err() << "Failed to deactivate shared context before sharing" << std::endl;
|
||||
err() << "Failed to deactivate shared context before sharing" << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -277,13 +277,13 @@ void SFContext::createContext(SFContext* shared, unsigned int bitsPerPixel, cons
|
||||
|
||||
if (m_context == nil)
|
||||
{
|
||||
sf::err() << "Error. Unable to create the context. Retrying without shared context." << std::endl;
|
||||
err() << "Error. Unable to create the context. Retrying without shared context." << std::endl;
|
||||
m_context = [[NSOpenGLContext alloc] initWithFormat:pixFmt shareContext:nil];
|
||||
|
||||
if (m_context == nil)
|
||||
sf::err() << "Error. Unable to create the context." << std::endl;
|
||||
err() << "Error. Unable to create the context." << std::endl;
|
||||
else
|
||||
sf::err() << "Warning. New context created without shared context." << std::endl;
|
||||
err() << "Warning. New context created without shared context." << std::endl;
|
||||
}
|
||||
|
||||
// Free up.
|
||||
|
@ -1,8 +1,209 @@
|
||||
#include <SFML/Audio/Music.hpp>
|
||||
|
||||
// Other 1st party headers
|
||||
#include <SFML/System/FileInputStream.hpp>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <AudioUtil.hpp>
|
||||
#include <SystemUtil.hpp>
|
||||
#include <array>
|
||||
#include <fstream>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
|
||||
static_assert(!std::is_copy_constructible_v<sf::Music>);
|
||||
static_assert(!std::is_copy_assignable_v<sf::Music>);
|
||||
static_assert(!std::is_nothrow_move_constructible_v<sf::Music>);
|
||||
static_assert(!std::is_nothrow_move_assignable_v<sf::Music>);
|
||||
TEST_CASE("[Audio] sf::Music", runAudioDeviceTests())
|
||||
{
|
||||
SECTION("Type traits")
|
||||
{
|
||||
STATIC_CHECK(!std::is_copy_constructible_v<sf::Music>);
|
||||
STATIC_CHECK(!std::is_copy_assignable_v<sf::Music>);
|
||||
STATIC_CHECK(!std::is_nothrow_move_constructible_v<sf::Music>);
|
||||
STATIC_CHECK(!std::is_nothrow_move_assignable_v<sf::Music>);
|
||||
STATIC_CHECK(std::has_virtual_destructor_v<sf::Music>);
|
||||
}
|
||||
|
||||
SECTION("Span")
|
||||
{
|
||||
const sf::Music::Span<float> span;
|
||||
CHECK(span.offset == 0);
|
||||
CHECK(span.length == 0);
|
||||
|
||||
const sf::Music::TimeSpan timeSpan;
|
||||
CHECK(timeSpan.offset == sf::Time::Zero);
|
||||
CHECK(timeSpan.length == sf::Time::Zero);
|
||||
}
|
||||
|
||||
SECTION("Construction")
|
||||
{
|
||||
const sf::Music music;
|
||||
CHECK(music.getDuration() == sf::Time::Zero);
|
||||
const auto [offset, length] = music.getLoopPoints();
|
||||
CHECK(offset == sf::Time::Zero);
|
||||
CHECK(length == sf::Time::Zero);
|
||||
CHECK(music.getChannelCount() == 0);
|
||||
CHECK(music.getSampleRate() == 0);
|
||||
CHECK(music.getStatus() == sf::Music::Status::Stopped);
|
||||
CHECK(music.getPlayingOffset() == sf::Time::Zero);
|
||||
CHECK(!music.getLoop());
|
||||
}
|
||||
|
||||
SECTION("openFromFile()")
|
||||
{
|
||||
sf::Music music;
|
||||
|
||||
SECTION("Invalid file")
|
||||
{
|
||||
REQUIRE(!music.openFromFile("does/not/exist.wav"));
|
||||
CHECK(music.getDuration() == sf::Time::Zero);
|
||||
const auto [offset, length] = music.getLoopPoints();
|
||||
CHECK(offset == sf::Time::Zero);
|
||||
CHECK(length == sf::Time::Zero);
|
||||
CHECK(music.getChannelCount() == 0);
|
||||
CHECK(music.getSampleRate() == 0);
|
||||
CHECK(music.getStatus() == sf::Music::Status::Stopped);
|
||||
CHECK(music.getPlayingOffset() == sf::Time::Zero);
|
||||
CHECK(!music.getLoop());
|
||||
}
|
||||
|
||||
SECTION("Valid file")
|
||||
{
|
||||
REQUIRE(music.openFromFile("Audio/ding.mp3"));
|
||||
CHECK(music.getDuration() == sf::microseconds(1990884));
|
||||
const auto [offset, length] = music.getLoopPoints();
|
||||
CHECK(offset == sf::Time::Zero);
|
||||
CHECK(length == sf::microseconds(1990884));
|
||||
CHECK(music.getChannelCount() == 1);
|
||||
CHECK(music.getSampleRate() == 44100);
|
||||
CHECK(music.getStatus() == sf::Music::Status::Stopped);
|
||||
CHECK(music.getPlayingOffset() == sf::Time::Zero);
|
||||
CHECK(!music.getLoop());
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("openFromMemory()")
|
||||
{
|
||||
std::vector<std::byte> memory;
|
||||
sf::Music music;
|
||||
|
||||
SECTION("Invalid buffer")
|
||||
{
|
||||
REQUIRE(!music.openFromMemory(memory.data(), memory.size()));
|
||||
CHECK(music.getDuration() == sf::Time::Zero);
|
||||
const auto [offset, length] = music.getLoopPoints();
|
||||
CHECK(offset == sf::Time::Zero);
|
||||
CHECK(length == sf::Time::Zero);
|
||||
CHECK(music.getChannelCount() == 0);
|
||||
CHECK(music.getSampleRate() == 0);
|
||||
CHECK(music.getStatus() == sf::Music::Status::Stopped);
|
||||
CHECK(music.getPlayingOffset() == sf::Time::Zero);
|
||||
CHECK(!music.getLoop());
|
||||
}
|
||||
|
||||
SECTION("Valid buffer")
|
||||
{
|
||||
memory = loadIntoMemory("Audio/ding.flac");
|
||||
REQUIRE(music.openFromMemory(memory.data(), memory.size()));
|
||||
CHECK(music.getDuration() == sf::microseconds(1990884));
|
||||
const auto [offset, length] = music.getLoopPoints();
|
||||
CHECK(offset == sf::Time::Zero);
|
||||
CHECK(length == sf::microseconds(1990884));
|
||||
CHECK(music.getChannelCount() == 1);
|
||||
CHECK(music.getSampleRate() == 44100);
|
||||
CHECK(music.getStatus() == sf::Music::Status::Stopped);
|
||||
CHECK(music.getPlayingOffset() == sf::Time::Zero);
|
||||
CHECK(!music.getLoop());
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("openFromStream()")
|
||||
{
|
||||
sf::FileInputStream stream;
|
||||
sf::Music music;
|
||||
|
||||
SECTION("Invalid stream")
|
||||
{
|
||||
CHECK(!music.openFromStream(stream));
|
||||
CHECK(music.getDuration() == sf::Time::Zero);
|
||||
const auto [offset, length] = music.getLoopPoints();
|
||||
CHECK(offset == sf::Time::Zero);
|
||||
CHECK(length == sf::Time::Zero);
|
||||
CHECK(music.getChannelCount() == 0);
|
||||
CHECK(music.getSampleRate() == 0);
|
||||
CHECK(music.getStatus() == sf::Music::Status::Stopped);
|
||||
CHECK(music.getPlayingOffset() == sf::Time::Zero);
|
||||
CHECK(!music.getLoop());
|
||||
}
|
||||
|
||||
SECTION("Valid stream")
|
||||
{
|
||||
REQUIRE(stream.open("Audio/doodle_pop.ogg"));
|
||||
REQUIRE(music.openFromStream(stream));
|
||||
CHECK(music.getDuration() == sf::microseconds(24002176));
|
||||
const auto [offset, length] = music.getLoopPoints();
|
||||
CHECK(offset == sf::Time::Zero);
|
||||
CHECK(length == sf::microseconds(24002176));
|
||||
CHECK(music.getChannelCount() == 2);
|
||||
CHECK(music.getSampleRate() == 44100);
|
||||
CHECK(music.getStatus() == sf::Music::Status::Stopped);
|
||||
CHECK(music.getPlayingOffset() == sf::Time::Zero);
|
||||
CHECK(!music.getLoop());
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("play/pause/stop")
|
||||
{
|
||||
sf::Music music;
|
||||
REQUIRE(music.openFromFile("Audio/ding.mp3"));
|
||||
|
||||
// Wait for background thread to start
|
||||
music.play();
|
||||
while (music.getStatus() == sf::Music::Status::Stopped)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
CHECK(music.getStatus() == sf::Music::Status::Playing);
|
||||
|
||||
// Wait for background thread to pause
|
||||
music.pause();
|
||||
while (music.getStatus() == sf::Music::Status::Playing)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
CHECK(music.getStatus() == sf::Music::Status::Paused);
|
||||
|
||||
// Wait for background thread to stop
|
||||
music.stop();
|
||||
while (music.getStatus() == sf::Music::Status::Paused)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
CHECK(music.getStatus() == sf::Music::Status::Stopped);
|
||||
}
|
||||
|
||||
SECTION("setLoopPoints()")
|
||||
{
|
||||
sf::Music music;
|
||||
|
||||
SECTION("No file")
|
||||
{
|
||||
music.setLoopPoints({sf::Time::Zero, sf::Time::Zero});
|
||||
const auto [offset, length] = music.getLoopPoints();
|
||||
CHECK(offset == sf::Time::Zero);
|
||||
CHECK(length == sf::Time::Zero);
|
||||
CHECK(music.getChannelCount() == 0);
|
||||
CHECK(music.getSampleRate() == 0);
|
||||
CHECK(music.getStatus() == sf::Music::Status::Stopped);
|
||||
CHECK(music.getPlayingOffset() == sf::Time::Zero);
|
||||
CHECK(!music.getLoop());
|
||||
}
|
||||
|
||||
SECTION("Loaded file")
|
||||
{
|
||||
REQUIRE(music.openFromFile("Audio/killdeer.wav"));
|
||||
music.setLoopPoints({sf::seconds(1), sf::seconds(2)});
|
||||
const auto [offset, length] = music.getLoopPoints();
|
||||
CHECK(offset == sf::seconds(1));
|
||||
CHECK(length == sf::seconds(2));
|
||||
CHECK(music.getChannelCount() == 1);
|
||||
CHECK(music.getSampleRate() == 22050);
|
||||
CHECK(music.getStatus() == sf::Music::Status::Stopped);
|
||||
CHECK(music.getPlayingOffset() == sf::Time::Zero);
|
||||
CHECK(!music.getLoop());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,86 @@
|
||||
#include <SFML/Audio/Sound.hpp>
|
||||
|
||||
// Other 1st party headers
|
||||
#include <SFML/Audio/SoundBuffer.hpp>
|
||||
|
||||
#include <SFML/System/Time.hpp>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <AudioUtil.hpp>
|
||||
#include <SystemUtil.hpp>
|
||||
#include <type_traits>
|
||||
|
||||
static_assert(!std::is_constructible_v<sf::Sound, sf::SoundBuffer&&>);
|
||||
static_assert(std::is_copy_constructible_v<sf::Sound>);
|
||||
static_assert(std::is_copy_assignable_v<sf::Sound>);
|
||||
static_assert(std::is_move_constructible_v<sf::Sound>);
|
||||
static_assert(!std::is_nothrow_move_constructible_v<sf::Sound>);
|
||||
static_assert(std::is_move_assignable_v<sf::Sound>);
|
||||
static_assert(!std::is_nothrow_move_assignable_v<sf::Sound>);
|
||||
TEST_CASE("[Audio] sf::Sound", runAudioDeviceTests())
|
||||
{
|
||||
SECTION("Type traits")
|
||||
{
|
||||
STATIC_CHECK(!std::is_constructible_v<sf::Sound, sf::SoundBuffer&&>);
|
||||
STATIC_CHECK(std::is_copy_constructible_v<sf::Sound>);
|
||||
STATIC_CHECK(std::is_copy_assignable_v<sf::Sound>);
|
||||
STATIC_CHECK(std::is_move_constructible_v<sf::Sound>);
|
||||
STATIC_CHECK(!std::is_nothrow_move_constructible_v<sf::Sound>);
|
||||
STATIC_CHECK(std::is_move_assignable_v<sf::Sound>);
|
||||
STATIC_CHECK(!std::is_nothrow_move_assignable_v<sf::Sound>);
|
||||
STATIC_CHECK(std::has_virtual_destructor_v<sf::Sound>);
|
||||
}
|
||||
|
||||
sf::SoundBuffer soundBuffer;
|
||||
REQUIRE(soundBuffer.loadFromFile("Audio/ding.flac"));
|
||||
|
||||
SECTION("Construction")
|
||||
{
|
||||
const sf::Sound sound(soundBuffer);
|
||||
CHECK(&sound.getBuffer() == &soundBuffer);
|
||||
CHECK(!sound.getLoop());
|
||||
CHECK(sound.getPlayingOffset() == sf::Time::Zero);
|
||||
CHECK(sound.getStatus() == sf::Sound::Status::Stopped);
|
||||
}
|
||||
|
||||
SECTION("Copy semantics")
|
||||
{
|
||||
const sf::Sound sound(soundBuffer);
|
||||
|
||||
SECTION("Construction")
|
||||
{
|
||||
const sf::Sound soundCopy(sound); // NOLINT(performance-unnecessary-copy-initialization)
|
||||
CHECK(&soundCopy.getBuffer() == &soundBuffer);
|
||||
CHECK(!soundCopy.getLoop());
|
||||
CHECK(soundCopy.getPlayingOffset() == sf::Time::Zero);
|
||||
CHECK(soundCopy.getStatus() == sf::Sound::Status::Stopped);
|
||||
}
|
||||
|
||||
SECTION("Assignment")
|
||||
{
|
||||
const sf::SoundBuffer emptySoundBuffer;
|
||||
sf::Sound soundCopy(emptySoundBuffer);
|
||||
soundCopy = sound;
|
||||
CHECK(&soundCopy.getBuffer() == &soundBuffer);
|
||||
CHECK(!soundCopy.getLoop());
|
||||
CHECK(soundCopy.getPlayingOffset() == sf::Time::Zero);
|
||||
CHECK(soundCopy.getStatus() == sf::Sound::Status::Stopped);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Set/get buffer")
|
||||
{
|
||||
const sf::SoundBuffer otherSoundBuffer;
|
||||
sf::Sound sound(soundBuffer);
|
||||
sound.setBuffer(otherSoundBuffer);
|
||||
CHECK(&sound.getBuffer() == &otherSoundBuffer);
|
||||
}
|
||||
|
||||
SECTION("Set/get loop")
|
||||
{
|
||||
sf::Sound sound(soundBuffer);
|
||||
sound.setLoop(true);
|
||||
CHECK(sound.getLoop());
|
||||
}
|
||||
|
||||
SECTION("Set/get playing offset")
|
||||
{
|
||||
sf::Sound sound(soundBuffer);
|
||||
sound.setPlayingOffset(sf::seconds(10));
|
||||
CHECK(sound.getPlayingOffset() == sf::seconds(10));
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,147 @@
|
||||
#include <SFML/Audio/SoundBuffer.hpp>
|
||||
|
||||
// Other 1st party headers
|
||||
#include <SFML/System/FileInputStream.hpp>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <AudioUtil.hpp>
|
||||
#include <SystemUtil.hpp>
|
||||
#include <array>
|
||||
#include <type_traits>
|
||||
|
||||
static_assert(std::is_copy_constructible_v<sf::SoundBuffer>);
|
||||
static_assert(std::is_copy_assignable_v<sf::SoundBuffer>);
|
||||
static_assert(std::is_move_constructible_v<sf::SoundBuffer>);
|
||||
static_assert(!std::is_nothrow_move_constructible_v<sf::SoundBuffer>);
|
||||
static_assert(std::is_move_assignable_v<sf::SoundBuffer>);
|
||||
static_assert(!std::is_nothrow_move_assignable_v<sf::SoundBuffer>);
|
||||
TEST_CASE("[Audio] sf::SoundBuffer", runAudioDeviceTests())
|
||||
{
|
||||
SECTION("Type traits")
|
||||
{
|
||||
STATIC_CHECK(std::is_copy_constructible_v<sf::SoundBuffer>);
|
||||
STATIC_CHECK(std::is_copy_assignable_v<sf::SoundBuffer>);
|
||||
STATIC_CHECK(std::is_move_constructible_v<sf::SoundBuffer>);
|
||||
STATIC_CHECK(!std::is_nothrow_move_constructible_v<sf::SoundBuffer>);
|
||||
STATIC_CHECK(std::is_move_assignable_v<sf::SoundBuffer>);
|
||||
STATIC_CHECK(!std::is_nothrow_move_assignable_v<sf::SoundBuffer>);
|
||||
}
|
||||
|
||||
SECTION("Construction")
|
||||
{
|
||||
const sf::SoundBuffer soundBuffer;
|
||||
CHECK(soundBuffer.getSamples() == nullptr);
|
||||
CHECK(soundBuffer.getSampleCount() == 0);
|
||||
CHECK(soundBuffer.getSampleRate() == 44100);
|
||||
CHECK(soundBuffer.getChannelCount() == 1);
|
||||
CHECK(soundBuffer.getDuration() == sf::Time::Zero);
|
||||
}
|
||||
|
||||
SECTION("Copy semantics")
|
||||
{
|
||||
sf::SoundBuffer soundBuffer;
|
||||
REQUIRE(soundBuffer.loadFromFile("Audio/ding.flac"));
|
||||
|
||||
SECTION("Construction")
|
||||
{
|
||||
const sf::SoundBuffer soundBufferCopy(soundBuffer); // NOLINT(performance-unnecessary-copy-initialization)
|
||||
CHECK(soundBufferCopy.getSamples() != nullptr);
|
||||
CHECK(soundBufferCopy.getSampleCount() == 87798);
|
||||
CHECK(soundBufferCopy.getSampleRate() == 44100);
|
||||
CHECK(soundBufferCopy.getChannelCount() == 1);
|
||||
CHECK(soundBufferCopy.getDuration() == sf::microseconds(1990884));
|
||||
}
|
||||
|
||||
SECTION("Assignment")
|
||||
{
|
||||
sf::SoundBuffer soundBufferCopy;
|
||||
soundBufferCopy = soundBuffer;
|
||||
CHECK(soundBufferCopy.getSamples() != nullptr);
|
||||
CHECK(soundBufferCopy.getSampleCount() == 87798);
|
||||
CHECK(soundBufferCopy.getSampleRate() == 44100);
|
||||
CHECK(soundBufferCopy.getChannelCount() == 1);
|
||||
CHECK(soundBufferCopy.getDuration() == sf::microseconds(1990884));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("loadFromFile()")
|
||||
{
|
||||
sf::SoundBuffer soundBuffer;
|
||||
|
||||
SECTION("Invalid filename")
|
||||
{
|
||||
CHECK(!soundBuffer.loadFromFile("does/not/exist.wav"));
|
||||
}
|
||||
|
||||
SECTION("Valid file")
|
||||
{
|
||||
REQUIRE(soundBuffer.loadFromFile("Audio/ding.flac"));
|
||||
CHECK(soundBuffer.getSamples() != nullptr);
|
||||
CHECK(soundBuffer.getSampleCount() == 87798);
|
||||
CHECK(soundBuffer.getSampleRate() == 44100);
|
||||
CHECK(soundBuffer.getChannelCount() == 1);
|
||||
CHECK(soundBuffer.getDuration() == sf::microseconds(1990884));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("loadFromMemory()")
|
||||
{
|
||||
sf::SoundBuffer soundBuffer;
|
||||
|
||||
SECTION("Invalid memory")
|
||||
{
|
||||
CHECK(!soundBuffer.loadFromMemory(nullptr, 0));
|
||||
constexpr std::array<std::byte, 5> memory{};
|
||||
CHECK(!soundBuffer.loadFromMemory(memory.data(), memory.size()));
|
||||
}
|
||||
|
||||
SECTION("Valid memory")
|
||||
{
|
||||
const auto memory = loadIntoMemory("Audio/ding.flac");
|
||||
REQUIRE(soundBuffer.loadFromMemory(memory.data(), memory.size()));
|
||||
CHECK(soundBuffer.getSamples() != nullptr);
|
||||
CHECK(soundBuffer.getSampleCount() == 87798);
|
||||
CHECK(soundBuffer.getSampleRate() == 44100);
|
||||
CHECK(soundBuffer.getChannelCount() == 1);
|
||||
CHECK(soundBuffer.getDuration() == sf::microseconds(1990884));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("loadFromStream()")
|
||||
{
|
||||
sf::FileInputStream stream;
|
||||
sf::SoundBuffer soundBuffer;
|
||||
|
||||
SECTION("Invalid stream")
|
||||
{
|
||||
CHECK(!soundBuffer.loadFromStream(stream));
|
||||
}
|
||||
|
||||
SECTION("Valid stream")
|
||||
{
|
||||
REQUIRE(stream.open("Audio/ding.flac"));
|
||||
REQUIRE(soundBuffer.loadFromStream(stream));
|
||||
CHECK(soundBuffer.getSamples() != nullptr);
|
||||
CHECK(soundBuffer.getSampleCount() == 87798);
|
||||
CHECK(soundBuffer.getSampleRate() == 44100);
|
||||
CHECK(soundBuffer.getChannelCount() == 1);
|
||||
CHECK(soundBuffer.getDuration() == sf::microseconds(1990884));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("saveToFile()")
|
||||
{
|
||||
const auto filename = std::filesystem::temp_directory_path() / "ding.flac";
|
||||
|
||||
{
|
||||
sf::SoundBuffer soundBuffer;
|
||||
REQUIRE(soundBuffer.loadFromFile("Audio/ding.flac"));
|
||||
REQUIRE(soundBuffer.saveToFile(filename));
|
||||
}
|
||||
|
||||
sf::SoundBuffer soundBuffer;
|
||||
REQUIRE(soundBuffer.loadFromFile(filename));
|
||||
CHECK(soundBuffer.getSamples() != nullptr);
|
||||
CHECK(soundBuffer.getSampleCount() == 87798);
|
||||
CHECK(soundBuffer.getSampleRate() == 44100);
|
||||
CHECK(soundBuffer.getChannelCount() == 1);
|
||||
CHECK(soundBuffer.getDuration() == sf::microseconds(1990884));
|
||||
|
||||
CHECK(std::filesystem::remove(filename));
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,134 @@
|
||||
#include <SFML/Audio/SoundSource.hpp>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <AudioUtil.hpp>
|
||||
#include <SystemUtil.hpp>
|
||||
#include <type_traits>
|
||||
|
||||
static_assert(!std::is_constructible_v<sf::SoundSource>);
|
||||
static_assert(!std::is_copy_constructible_v<sf::SoundSource>);
|
||||
static_assert(std::is_copy_assignable_v<sf::SoundSource>);
|
||||
static_assert(!std::is_move_constructible_v<sf::SoundSource>);
|
||||
static_assert(std::is_move_assignable_v<sf::SoundSource>);
|
||||
static_assert(!std::is_nothrow_move_assignable_v<sf::SoundSource>);
|
||||
namespace
|
||||
{
|
||||
class SoundSource : public sf::SoundSource
|
||||
{
|
||||
void play() override
|
||||
{
|
||||
}
|
||||
|
||||
void pause() override
|
||||
{
|
||||
}
|
||||
|
||||
void stop() override
|
||||
{
|
||||
}
|
||||
|
||||
void* getSound() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
public:
|
||||
Status getStatus() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("[Audio] sf::SoundSource", runAudioDeviceTests())
|
||||
{
|
||||
SECTION("Type traits")
|
||||
{
|
||||
STATIC_CHECK(!std::is_constructible_v<sf::SoundSource>);
|
||||
STATIC_CHECK(!std::is_copy_constructible_v<sf::SoundSource>);
|
||||
STATIC_CHECK(std::is_copy_assignable_v<sf::SoundSource>);
|
||||
STATIC_CHECK(!std::is_move_constructible_v<sf::SoundSource>);
|
||||
STATIC_CHECK(std::is_move_assignable_v<sf::SoundSource>);
|
||||
STATIC_CHECK(!std::is_nothrow_move_assignable_v<sf::SoundSource>);
|
||||
STATIC_CHECK(std::has_virtual_destructor_v<sf::SoundSource>);
|
||||
}
|
||||
|
||||
SECTION("Construction")
|
||||
{
|
||||
const SoundSource soundSource;
|
||||
CHECK(soundSource.getPitch() == 0);
|
||||
CHECK(soundSource.getVolume() == 0);
|
||||
CHECK(soundSource.getPosition() == sf::Vector3f());
|
||||
CHECK(!soundSource.isRelativeToListener());
|
||||
CHECK(soundSource.getMinDistance() == 0);
|
||||
CHECK(soundSource.getAttenuation() == 0);
|
||||
CHECK(soundSource.getStatus() == sf::SoundSource::Status::Stopped);
|
||||
}
|
||||
|
||||
SECTION("Copy semantics")
|
||||
{
|
||||
const SoundSource soundSource;
|
||||
|
||||
SECTION("Construction")
|
||||
{
|
||||
const SoundSource soundSourceCopy(soundSource); // NOLINT(performance-unnecessary-copy-initialization)
|
||||
CHECK(soundSourceCopy.getPitch() == 0);
|
||||
CHECK(soundSourceCopy.getVolume() == 0);
|
||||
CHECK(soundSourceCopy.getPosition() == sf::Vector3f());
|
||||
CHECK(!soundSourceCopy.isRelativeToListener());
|
||||
CHECK(soundSourceCopy.getMinDistance() == 0);
|
||||
CHECK(soundSourceCopy.getAttenuation() == 0);
|
||||
CHECK(soundSourceCopy.getStatus() == sf::SoundSource::Status::Stopped);
|
||||
}
|
||||
|
||||
SECTION("Assignment")
|
||||
{
|
||||
SoundSource soundSourceCopy;
|
||||
soundSourceCopy = soundSource;
|
||||
CHECK(soundSourceCopy.getPitch() == 0);
|
||||
CHECK(soundSourceCopy.getVolume() == 0);
|
||||
CHECK(soundSourceCopy.getPosition() == sf::Vector3f());
|
||||
CHECK(!soundSourceCopy.isRelativeToListener());
|
||||
CHECK(soundSourceCopy.getMinDistance() == 0);
|
||||
CHECK(soundSourceCopy.getAttenuation() == 0);
|
||||
CHECK(soundSourceCopy.getStatus() == sf::SoundSource::Status::Stopped);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Set/get pitch")
|
||||
{
|
||||
SoundSource soundSource;
|
||||
soundSource.setPitch(42);
|
||||
CHECK(soundSource.getPitch() == 0);
|
||||
}
|
||||
|
||||
SECTION("Set/get volume")
|
||||
{
|
||||
SoundSource soundSource;
|
||||
soundSource.setVolume(0.5f);
|
||||
CHECK(soundSource.getVolume() == 0);
|
||||
}
|
||||
|
||||
SECTION("Set/get position")
|
||||
{
|
||||
SoundSource soundSource;
|
||||
soundSource.setPosition({1, 2, 3});
|
||||
CHECK(soundSource.getPosition() == sf::Vector3f());
|
||||
}
|
||||
|
||||
SECTION("Set/get relative to listener")
|
||||
{
|
||||
SoundSource soundSource;
|
||||
soundSource.setRelativeToListener(true);
|
||||
CHECK(!soundSource.isRelativeToListener());
|
||||
}
|
||||
|
||||
SECTION("Set/get min distance")
|
||||
{
|
||||
SoundSource soundSource;
|
||||
soundSource.setMinDistance(12.34f);
|
||||
CHECK(soundSource.getMinDistance() == 0);
|
||||
}
|
||||
|
||||
SECTION("Set/get attenuation")
|
||||
{
|
||||
SoundSource soundSource;
|
||||
soundSource.setAttenuation(10);
|
||||
CHECK(soundSource.getAttenuation() == 0);
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,26 @@
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <AudioUtil.hpp>
|
||||
#include <SystemUtil.hpp>
|
||||
#include <type_traits>
|
||||
|
||||
TEST_CASE("[Audio] sf::SoundStream")
|
||||
namespace
|
||||
{
|
||||
class SoundStream : public sf::SoundStream
|
||||
{
|
||||
[[nodiscard]] bool onGetData(Chunk& /* data */) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void onSeek(sf::Time /* timeOffset */) override
|
||||
{
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("[Audio] sf::SoundStream", runAudioDeviceTests())
|
||||
{
|
||||
SECTION("Type traits")
|
||||
{
|
||||
@ -13,6 +30,7 @@ TEST_CASE("[Audio] sf::SoundStream")
|
||||
STATIC_CHECK(!std::is_copy_assignable_v<sf::SoundStream>);
|
||||
STATIC_CHECK(!std::is_nothrow_move_constructible_v<sf::SoundStream>);
|
||||
STATIC_CHECK(!std::is_nothrow_move_assignable_v<sf::SoundStream>);
|
||||
STATIC_CHECK(std::has_virtual_destructor_v<sf::SoundStream>);
|
||||
}
|
||||
|
||||
SECTION("Chunk")
|
||||
@ -21,4 +39,28 @@ TEST_CASE("[Audio] sf::SoundStream")
|
||||
CHECK(chunk.samples == nullptr);
|
||||
CHECK(chunk.sampleCount == 0);
|
||||
}
|
||||
|
||||
SECTION("Construction")
|
||||
{
|
||||
const SoundStream soundStream;
|
||||
CHECK(soundStream.getChannelCount() == 0);
|
||||
CHECK(soundStream.getSampleRate() == 0);
|
||||
CHECK(soundStream.getStatus() == sf::SoundStream::Status::Stopped);
|
||||
CHECK(soundStream.getPlayingOffset() == sf::Time::Zero);
|
||||
CHECK(!soundStream.getLoop());
|
||||
}
|
||||
|
||||
SECTION("Set/get playing offset")
|
||||
{
|
||||
SoundStream soundStream;
|
||||
soundStream.setPlayingOffset(sf::milliseconds(100));
|
||||
CHECK(soundStream.getPlayingOffset() == sf::milliseconds(0));
|
||||
}
|
||||
|
||||
SECTION("Set/get loop")
|
||||
{
|
||||
SoundStream soundStream;
|
||||
soundStream.setLoop(true);
|
||||
CHECK(soundStream.getLoop());
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ set(CATCH_CONFIG_FAST_COMPILE ON CACHE BOOL "")
|
||||
set(CATCH_CONFIG_NO_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT ON CACHE BOOL "")
|
||||
FetchContent_Declare(Catch2
|
||||
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
||||
GIT_TAG v3.5.4
|
||||
GIT_TAG v3.6.0
|
||||
GIT_SHALLOW ON)
|
||||
FetchContent_MakeAvailable(Catch2)
|
||||
include(Catch)
|
||||
@ -28,6 +28,8 @@ add_library(sfml-test-main STATIC
|
||||
TestUtilities/WindowUtil.cpp
|
||||
TestUtilities/GraphicsUtil.hpp
|
||||
TestUtilities/GraphicsUtil.cpp
|
||||
TestUtilities/AudioUtil.hpp
|
||||
TestUtilities/AudioUtil.cpp
|
||||
)
|
||||
target_include_directories(sfml-test-main PUBLIC TestUtilities)
|
||||
target_link_libraries(sfml-test-main PUBLIC SFML::System Catch2::Catch2WithMain)
|
||||
@ -43,6 +45,11 @@ if(SFML_RUN_DISPLAY_TESTS)
|
||||
target_compile_definitions(sfml-test-main PRIVATE SFML_RUN_DISPLAY_TESTS)
|
||||
endif()
|
||||
|
||||
sfml_set_option(SFML_RUN_AUDIO_DEVICE_TESTS ON BOOL "TRUE to run tests that require an audio device, FALSE to ignore it")
|
||||
if(SFML_RUN_AUDIO_DEVICE_TESTS)
|
||||
target_compile_definitions(sfml-test-main PRIVATE SFML_RUN_AUDIO_DEVICE_TESTS)
|
||||
endif()
|
||||
|
||||
set(SYSTEM_SRC
|
||||
System/Angle.test.cpp
|
||||
System/Clock.test.cpp
|
||||
@ -145,6 +152,41 @@ set(AUDIO_SRC
|
||||
)
|
||||
sfml_add_test(test-sfml-audio "${AUDIO_SRC}" SFML::Audio)
|
||||
|
||||
if(SFML_OS_ANDROID AND DEFINED ENV{LIBCXX_SHARED_SO})
|
||||
# Because we can only write to the tmp directory on the Android virtual device we will need to build our directory tree under it
|
||||
set(TARGET_DIR "/data/local/tmp/$<TARGET_FILE_DIR:test-sfml-system>")
|
||||
|
||||
# Generate script that copies necessary files over to the Android virtual device
|
||||
file(GENERATE OUTPUT "${PROJECT_BINARY_DIR}/prepare-android-files.sh" CONTENT
|
||||
"#!/bin/bash\n\
|
||||
adb shell \"mkdir -p ${TARGET_DIR}\"\n\
|
||||
adb push $<TARGET_FILE:test-sfml-audio> ${TARGET_DIR}\n\
|
||||
adb push $<TARGET_FILE:test-sfml-graphics> ${TARGET_DIR}\n\
|
||||
adb push $<TARGET_FILE:test-sfml-network> ${TARGET_DIR}\n\
|
||||
adb push $<TARGET_FILE:test-sfml-system> ${TARGET_DIR}\n\
|
||||
adb push $<TARGET_FILE:test-sfml-window> ${TARGET_DIR}\n\
|
||||
adb push $<TARGET_FILE:SFML::Audio> ${TARGET_DIR}\n\
|
||||
adb push $<TARGET_FILE:SFML::Graphics> ${TARGET_DIR}\n\
|
||||
adb push $<TARGET_FILE:SFML::Network> ${TARGET_DIR}\n\
|
||||
adb push $<TARGET_FILE:SFML::System> ${TARGET_DIR}\n\
|
||||
adb push $<TARGET_FILE:SFML::Window> ${TARGET_DIR}\n\
|
||||
adb push $<TARGET_FILE:Catch2> ${TARGET_DIR}\n\
|
||||
adb push $<TARGET_FILE:Catch2WithMain> ${TARGET_DIR}\n\
|
||||
adb push $ENV{LIBCXX_SHARED_SO} ${TARGET_DIR}\n\
|
||||
adb push ${CMAKE_CURRENT_LIST_DIR} ${TARGET_DIR}\n\
|
||||
adb shell \"chmod -R 775 ${TARGET_DIR} && ls -la ${TARGET_DIR}\"\n"
|
||||
FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE)
|
||||
|
||||
# Add the target to invoke the file copy script
|
||||
add_custom_target(prepare-android-files COMMAND "${PROJECT_BINARY_DIR}/prepare-android-files.sh")
|
||||
|
||||
# Generate proxy script that translates CTest commands into adb shell commands
|
||||
file(GENERATE OUTPUT "${PROJECT_BINARY_DIR}/run-in-adb-shell.sh" CONTENT
|
||||
"#!/bin/bash\n\
|
||||
adb shell \"cd ${TARGET_DIR}/test; LD_LIBRARY_PATH=${TARGET_DIR} /data/local/tmp/$1 \\\"$2\\\" \\\"$3\\\" \\\"$4\\\" \\\"$5\\\" \\\"$6\\\" \\\"$7\\\" \\\"$8\\\" \\\"$9\\\"\"\n"
|
||||
FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE)
|
||||
endif()
|
||||
|
||||
if(SFML_ENABLE_COVERAGE AND SFML_OS_WINDOWS AND NOT SFML_COMPILER_GCC)
|
||||
# Try to find and use OpenCppCoverage for coverage reporting when building with MSVC
|
||||
find_program(OpenCppCoverage_BINARY "OpenCppCoverage.exe")
|
||||
|
@ -34,11 +34,11 @@ TEST_CASE("[Graphics] sf::Transformable")
|
||||
CHECK(transformable.getPosition() == sf::Vector2f(3, 4));
|
||||
|
||||
transformable.setRotation(sf::degrees(3.14f));
|
||||
CHECK(transformable.getRotation() == sf::degrees(3.14f));
|
||||
CHECK(transformable.getRotation() == Approx(sf::degrees(3.14f)));
|
||||
transformable.setRotation(sf::degrees(540));
|
||||
CHECK(transformable.getRotation() == sf::degrees(180));
|
||||
CHECK(transformable.getRotation() == Approx(sf::degrees(180)));
|
||||
transformable.setRotation(sf::degrees(-72));
|
||||
CHECK(transformable.getRotation() == sf::degrees(288));
|
||||
CHECK(transformable.getRotation() == Approx(sf::degrees(288)));
|
||||
|
||||
transformable.setScale({5, 6});
|
||||
CHECK(transformable.getScale() == sf::Vector2f(5, 6));
|
||||
@ -102,15 +102,15 @@ TEST_CASE("[Graphics] sf::Transformable")
|
||||
sf::Transformable transformable;
|
||||
CHECK(transformable.getRotation() == sf::Angle::Zero);
|
||||
transformable.rotate(sf::degrees(15));
|
||||
CHECK(transformable.getRotation() == sf::degrees(15));
|
||||
CHECK(transformable.getRotation() == Approx(sf::degrees(15)));
|
||||
transformable.rotate(sf::degrees(360));
|
||||
CHECK(transformable.getRotation() == sf::degrees(15));
|
||||
CHECK(transformable.getRotation() == Approx(sf::degrees(15)));
|
||||
transformable.rotate(sf::degrees(-25));
|
||||
CHECK(transformable.getRotation() == sf::degrees(350));
|
||||
CHECK(transformable.getRotation() == Approx(sf::degrees(350)));
|
||||
transformable.rotate(sf::degrees(-720));
|
||||
CHECK(transformable.getRotation() == sf::degrees(350));
|
||||
CHECK(transformable.getRotation() == Approx(sf::degrees(350)));
|
||||
transformable.rotate(sf::degrees(-370));
|
||||
CHECK(transformable.getRotation() == sf::degrees(340));
|
||||
CHECK(transformable.getRotation() == Approx(sf::degrees(340)));
|
||||
}
|
||||
|
||||
SECTION("scale()")
|
||||
|
@ -76,13 +76,13 @@ TEST_CASE("[Graphics] sf::View")
|
||||
{
|
||||
sf::View view;
|
||||
view.setRotation(sf::degrees(-345));
|
||||
CHECK(view.getRotation() == sf::degrees(15));
|
||||
CHECK(view.getRotation() == Approx(sf::degrees(15)));
|
||||
CHECK(view.getTransform() ==
|
||||
Approx(sf::Transform(0.00193185f, 0.000517638f, -1.22474f, 0.000517638f, -0.00193185f, 0.707107f, 0, 0, 1)));
|
||||
CHECK(view.getInverseTransform() ==
|
||||
Approx(sf::Transform(482.963f, 129.41f, 500, 129.41f, -482.963f, 500, 0, 0, 1)));
|
||||
view.setRotation(sf::degrees(400));
|
||||
CHECK(view.getRotation() == sf::degrees(40));
|
||||
CHECK(view.getRotation() == Approx(sf::degrees(40)));
|
||||
CHECK(view.getTransform() ==
|
||||
Approx(sf::Transform(0.00153209f, 0.00128558f, -1.40883f, 0.00128558f, -0.00153209f, 0.123257f, 0, 0, 1)));
|
||||
CHECK(view.getInverseTransform() ==
|
||||
@ -140,7 +140,7 @@ TEST_CASE("[Graphics] sf::View")
|
||||
sf::View view;
|
||||
view.setRotation(sf::degrees(45));
|
||||
view.rotate(sf::degrees(-15));
|
||||
CHECK(view.getRotation() == sf::degrees(30));
|
||||
CHECK(view.getRotation() == Approx(sf::degrees(30)));
|
||||
CHECK(view.getTransform() ==
|
||||
Approx(sf::Transform(0.00173205f, 0.001f, -1.36603f, 0.001f, -0.00173205f, 0.366025f, 0, 0, 1)));
|
||||
CHECK(view.getInverseTransform() == Approx(sf::Transform(433.013f, 250, 500, 250, -433.013f, 500, 0, 0, 1)));
|
||||
|
@ -25,33 +25,33 @@ TEST_CASE("[System] sf::Angle")
|
||||
SECTION("wrapSigned()")
|
||||
{
|
||||
STATIC_CHECK(sf::Angle::Zero.wrapSigned() == sf::Angle::Zero);
|
||||
STATIC_CHECK(sf::degrees(0).wrapSigned() == sf::degrees(0));
|
||||
STATIC_CHECK(sf::degrees(1).wrapSigned() == sf::degrees(1));
|
||||
STATIC_CHECK(sf::degrees(-1).wrapSigned() == sf::degrees(-1));
|
||||
STATIC_CHECK(sf::degrees(90).wrapSigned() == sf::degrees(90));
|
||||
STATIC_CHECK(sf::degrees(-90).wrapSigned() == sf::degrees(-90));
|
||||
STATIC_CHECK(sf::degrees(180).wrapSigned() == sf::degrees(-180));
|
||||
STATIC_CHECK(sf::degrees(-180).wrapSigned() == sf::degrees(-180));
|
||||
STATIC_CHECK(sf::degrees(360).wrapSigned() == sf::degrees(0));
|
||||
STATIC_CHECK(sf::degrees(-360).wrapSigned() == sf::degrees(0));
|
||||
STATIC_CHECK(sf::degrees(720).wrapSigned() == sf::degrees(0));
|
||||
STATIC_CHECK(sf::degrees(-720).wrapSigned() == sf::degrees(0));
|
||||
CHECK(sf::degrees(0).wrapSigned() == Approx(sf::degrees(0)));
|
||||
CHECK(sf::degrees(1).wrapSigned() == Approx(sf::degrees(1)));
|
||||
CHECK(sf::degrees(-1).wrapSigned() == Approx(sf::degrees(-1)));
|
||||
CHECK(sf::degrees(90).wrapSigned() == Approx(sf::degrees(90)));
|
||||
CHECK(sf::degrees(-90).wrapSigned() == Approx(sf::degrees(-90)));
|
||||
CHECK(sf::degrees(180).wrapSigned() == Approx(sf::degrees(-180)));
|
||||
CHECK(sf::degrees(-180).wrapSigned() == Approx(sf::degrees(-180)));
|
||||
CHECK(sf::degrees(360).wrapSigned() == Approx(sf::degrees(0)));
|
||||
CHECK(sf::degrees(-360).wrapSigned() == Approx(sf::degrees(0)));
|
||||
CHECK(sf::degrees(720).wrapSigned() == Approx(sf::degrees(0)));
|
||||
CHECK(sf::degrees(-720).wrapSigned() == Approx(sf::degrees(0)));
|
||||
}
|
||||
|
||||
SECTION("wrapUnsigned()")
|
||||
{
|
||||
STATIC_CHECK(sf::Angle::Zero.wrapUnsigned() == sf::Angle::Zero);
|
||||
STATIC_CHECK(sf::degrees(0).wrapUnsigned() == sf::degrees(0));
|
||||
STATIC_CHECK(sf::degrees(1).wrapUnsigned() == sf::degrees(1));
|
||||
STATIC_CHECK(sf::degrees(-1).wrapUnsigned() == sf::degrees(359));
|
||||
STATIC_CHECK(sf::degrees(90).wrapUnsigned() == sf::degrees(90));
|
||||
STATIC_CHECK(sf::degrees(-90).wrapUnsigned() == sf::degrees(270));
|
||||
STATIC_CHECK(sf::degrees(180).wrapUnsigned() == sf::degrees(180));
|
||||
STATIC_CHECK(sf::degrees(-180).wrapUnsigned() == sf::degrees(180));
|
||||
STATIC_CHECK(sf::degrees(360).wrapUnsigned() == sf::degrees(0));
|
||||
STATIC_CHECK(sf::degrees(-360).wrapUnsigned() == sf::degrees(0));
|
||||
STATIC_CHECK(sf::degrees(720).wrapUnsigned() == sf::degrees(0));
|
||||
STATIC_CHECK(sf::degrees(-720).wrapUnsigned() == sf::degrees(0));
|
||||
CHECK(sf::degrees(0).wrapUnsigned() == Approx(sf::degrees(0)));
|
||||
CHECK(sf::degrees(1).wrapUnsigned() == Approx(sf::degrees(1)));
|
||||
CHECK(sf::degrees(-1).wrapUnsigned() == Approx(sf::degrees(359)));
|
||||
CHECK(sf::degrees(90).wrapUnsigned() == Approx(sf::degrees(90)));
|
||||
CHECK(sf::degrees(-90).wrapUnsigned() == Approx(sf::degrees(270)));
|
||||
CHECK(sf::degrees(180).wrapUnsigned() == Approx(sf::degrees(180)));
|
||||
CHECK(sf::degrees(-180).wrapUnsigned() == Approx(sf::degrees(180)));
|
||||
CHECK(sf::degrees(360).wrapUnsigned() == Approx(sf::degrees(0)));
|
||||
CHECK(sf::degrees(-360).wrapUnsigned() == Approx(sf::degrees(0)));
|
||||
CHECK(sf::degrees(720).wrapUnsigned() == Approx(sf::degrees(0)));
|
||||
CHECK(sf::degrees(-720).wrapUnsigned() == Approx(sf::degrees(0)));
|
||||
}
|
||||
|
||||
SECTION("degrees()")
|
||||
@ -185,7 +185,7 @@ TEST_CASE("[System] sf::Angle")
|
||||
{
|
||||
sf::Angle angle = sf::degrees(-15);
|
||||
angle += sf::degrees(15);
|
||||
CHECK(angle == sf::degrees(0));
|
||||
CHECK(angle == Approx(sf::degrees(0)));
|
||||
angle += sf::radians(10);
|
||||
CHECK(angle == sf::radians(10));
|
||||
}
|
||||
@ -202,7 +202,7 @@ TEST_CASE("[System] sf::Angle")
|
||||
{
|
||||
sf::Angle angle = sf::degrees(15);
|
||||
angle -= sf::degrees(15);
|
||||
CHECK(angle == sf::degrees(0));
|
||||
CHECK(angle == Approx(sf::degrees(0)));
|
||||
angle -= sf::radians(10);
|
||||
CHECK(angle == sf::radians(-10));
|
||||
}
|
||||
@ -210,19 +210,19 @@ TEST_CASE("[System] sf::Angle")
|
||||
SECTION("operator*")
|
||||
{
|
||||
STATIC_CHECK(sf::radians(0) * 10 == sf::Angle::Zero);
|
||||
STATIC_CHECK(sf::degrees(10) * 2.5f == sf::degrees(25));
|
||||
STATIC_CHECK(sf::degrees(100) * 10.0f == sf::degrees(1000));
|
||||
CHECK(sf::degrees(10) * 2.5f == Approx(sf::degrees(25)));
|
||||
CHECK(sf::degrees(100) * 10.0f == Approx(sf::degrees(1000)));
|
||||
|
||||
STATIC_CHECK(10 * sf::radians(0) == sf::Angle::Zero);
|
||||
STATIC_CHECK(2.5f * sf::degrees(10) == sf::degrees(25));
|
||||
STATIC_CHECK(10.0f * sf::degrees(100) == sf::degrees(1000));
|
||||
CHECK(2.5f * sf::degrees(10) == Approx(sf::degrees(25)));
|
||||
CHECK(10.0f * sf::degrees(100) == Approx(sf::degrees(1000)));
|
||||
}
|
||||
|
||||
SECTION("operator*=")
|
||||
{
|
||||
sf::Angle angle = sf::degrees(1);
|
||||
angle *= 10;
|
||||
CHECK(angle == sf::degrees(10));
|
||||
CHECK(angle == Approx(sf::degrees(10)));
|
||||
}
|
||||
|
||||
SECTION("operator/")
|
||||
@ -240,24 +240,24 @@ TEST_CASE("[System] sf::Angle")
|
||||
{
|
||||
sf::Angle angle = sf::degrees(60);
|
||||
angle /= 5;
|
||||
CHECK(angle == sf::degrees(12));
|
||||
CHECK(angle == Approx(sf::degrees(12)));
|
||||
}
|
||||
|
||||
SECTION("operator%")
|
||||
{
|
||||
STATIC_CHECK(sf::Angle::Zero % sf::radians(0.5f) == sf::Angle::Zero);
|
||||
STATIC_CHECK(sf::radians(10) % sf::radians(1) == sf::radians(0));
|
||||
STATIC_CHECK(sf::degrees(90) % sf::degrees(30) == sf::degrees(0));
|
||||
STATIC_CHECK(sf::degrees(90) % sf::degrees(40) == sf::degrees(10));
|
||||
STATIC_CHECK(sf::degrees(-90) % sf::degrees(30) == sf::degrees(0));
|
||||
STATIC_CHECK(sf::degrees(-90) % sf::degrees(40) == sf::degrees(30));
|
||||
CHECK(sf::degrees(90) % sf::degrees(30) == Approx(sf::degrees(0)));
|
||||
CHECK(sf::degrees(90) % sf::degrees(40) == Approx(sf::degrees(10)));
|
||||
CHECK(sf::degrees(-90) % sf::degrees(30) == Approx(sf::degrees(0)));
|
||||
CHECK(sf::degrees(-90) % sf::degrees(40) == Approx(sf::degrees(30)));
|
||||
}
|
||||
|
||||
SECTION("operator%=")
|
||||
{
|
||||
sf::Angle angle = sf::degrees(59);
|
||||
angle %= sf::degrees(10);
|
||||
CHECK(angle == sf::degrees(9));
|
||||
CHECK(angle == Approx(sf::degrees(9)));
|
||||
}
|
||||
|
||||
SECTION("operator _deg")
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <ostream>
|
||||
#include <string_view>
|
||||
|
||||
@ -32,9 +33,9 @@ TEST_CASE("[System] sf::MemoryInputStream")
|
||||
sf::MemoryInputStream mis;
|
||||
mis.open(memoryContents.data(), sizeof(char) * memoryContents.size());
|
||||
|
||||
char buffer[32];
|
||||
CHECK(mis.read(buffer, 5) == 5);
|
||||
CHECK(std::string_view(buffer, 5) == std::string_view(memoryContents.data(), 5));
|
||||
std::array<char, 32> buffer{};
|
||||
CHECK(mis.read(buffer.data(), 5) == 5);
|
||||
CHECK(std::string_view(buffer.data(), 5) == std::string_view(memoryContents.data(), 5));
|
||||
CHECK(mis.seek(10) == 10);
|
||||
CHECK(mis.tell() == 10);
|
||||
CHECK(mis.getSize() == 11);
|
||||
|
12
test/TestUtilities/AudioUtil.cpp
Normal file
12
test/TestUtilities/AudioUtil.cpp
Normal file
@ -0,0 +1,12 @@
|
||||
#include <AudioUtil.hpp>
|
||||
|
||||
std::string runAudioDeviceTests()
|
||||
{
|
||||
#ifdef SFML_RUN_AUDIO_DEVICE_TESTS
|
||||
return "";
|
||||
#else
|
||||
// https://github.com/catchorg/Catch2/blob/devel/docs/test-cases-and-sections.md#special-tags
|
||||
// This tag tells Catch2 to not run a given TEST_CASE
|
||||
return "[.audio_device]";
|
||||
#endif
|
||||
}
|
5
test/TestUtilities/AudioUtil.hpp
Normal file
5
test/TestUtilities/AudioUtil.hpp
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
[[nodiscard]] std::string runAudioDeviceTests();
|
@ -77,7 +77,7 @@ bool operator==(const sf::Vector3f& lhs, const Approx<sf::Vector3f>& rhs)
|
||||
|
||||
bool operator==(const sf::Angle& lhs, const Approx<sf::Angle>& rhs)
|
||||
{
|
||||
return lhs.asDegrees() == Approx(rhs.value.asDegrees());
|
||||
return lhs.asRadians() == Approx(rhs.value.asRadians());
|
||||
}
|
||||
|
||||
std::vector<std::byte> loadIntoMemory(const std::filesystem::path& path)
|
||||
|
@ -1,6 +1,6 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
|
||||
libdir=${exec_prefix}/@SFML_RELATIVE_INSTALL_LIBDIR@
|
||||
includedir=${prefix}/include
|
||||
|
||||
Name: SFML-all
|
||||
|
@ -1,6 +1,6 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
|
||||
libdir=${exec_prefix}/@SFML_RELATIVE_INSTALL_LIBDIR@
|
||||
includedir=${prefix}/include
|
||||
|
||||
Name: SFML-audio
|
||||
|
@ -1,6 +1,6 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
|
||||
libdir=${exec_prefix}/@SFML_RELATIVE_INSTALL_LIBDIR@
|
||||
includedir=${prefix}/include
|
||||
|
||||
Name: SFML-graphics
|
||||
|
@ -1,6 +1,6 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
|
||||
libdir=${exec_prefix}/@SFML_RELATIVE_INSTALL_LIBDIR@
|
||||
includedir=${prefix}/include
|
||||
|
||||
Name: SFML-network
|
||||
|
@ -1,6 +1,6 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
|
||||
libdir=${exec_prefix}/@SFML_RELATIVE_INSTALL_LIBDIR@
|
||||
includedir=${prefix}/include
|
||||
|
||||
Name: SFML-system
|
||||
|
@ -1,6 +1,6 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
|
||||
libdir=${exec_prefix}/@SFML_RELATIVE_INSTALL_LIBDIR@
|
||||
includedir=${prefix}/include
|
||||
|
||||
Name: SFML-window
|
||||
|
Loading…
Reference in New Issue
Block a user