Use 'atexit' registration

This commit is contained in:
vittorioromeo 2024-05-19 19:18:55 +02:00
commit 7229a6de15
88 changed files with 2618 additions and 1033 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 &copy; Laurent Gomila &nbsp;::&nbsp;
Documentation generated by <a href="http://www.doxygen.org/" title="doxygen website">doxygen</a> &nbsp;::&nbsp;
</div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -29,6 +29,7 @@
////////////////////////////////////////////////////////////
#include <SFML/Config.hpp>
#include <array>
#include <locale>
#include <cstdint>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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};
};
////////////////////////////////////////////////////////////

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}();

View File

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

View File

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

View File

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

View File

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

View File

@ -45,6 +45,7 @@
#include <SFML/System/Vector2.hpp>
#include <algorithm>
#include <array>
#include <chrono>
#include <filesystem>
#include <iostream>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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());
}
}
}

View File

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

View File

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

View File

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

View File

@ -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());
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@ -0,0 +1,5 @@
#pragma once
#include <string>
[[nodiscard]] std::string runAudioDeviceTests();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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