mirror of
https://github.com/SFML/SFML.git
synced 2024-11-25 04:41:05 +08:00
Use 'atexit' registration
This commit is contained in:
commit
7229a6de15
121
.github/workflows/ci.yml
vendored
121
.github/workflows/ci.yml
vendored
@ -9,6 +9,7 @@ concurrency:
|
|||||||
env:
|
env:
|
||||||
DISPLAY: ":99" # Display number to use for the X server
|
DISPLAY: ":99" # Display number to use for the X server
|
||||||
GALLIUM_DRIVER: llvmpipe # Use Mesa 3D software OpenGL renderer
|
GALLIUM_DRIVER: llvmpipe # Use Mesa 3D software OpenGL renderer
|
||||||
|
ANDROID_NDK_VERSION: "26.1.10909125" # Android NDK version to use
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
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 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 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: 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 GCC, os: ubuntu-22.04, flags: -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 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 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: 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, os: macos-12, flags: -GNinja }
|
||||||
- { name: macOS x64 Xcode, os: macos-12, flags: -GXcode }
|
- { 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, 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 }
|
- { name: iOS Xcode, os: macos-12, flags: -DCMAKE_SYSTEM_NAME=iOS -GXcode -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=NO }
|
||||||
config:
|
config:
|
||||||
@ -55,29 +56,56 @@ jobs:
|
|||||||
- platform: { name: Windows VS2022 x64, os: windows-2022 }
|
- 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 }
|
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 }
|
- 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 }
|
- 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 }
|
- 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 }
|
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 }
|
- 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 }
|
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 }
|
- platform: { name: macOS, os: macos-12 }
|
||||||
config: { name: Frameworks, flags: -GNinja -DSFML_BUILD_FRAMEWORKS=TRUE -DBUILD_SHARED_LIBS=TRUE }
|
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 }
|
- platform: { name: macOS , os: macos-12 }
|
||||||
config: { name: System Deps, flags: -GNinja -DBUILD_SHARED_LIBS=TRUE -DSFML_USE_SYSTEM_DEPS=TRUE }
|
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:
|
steps:
|
||||||
@ -107,11 +135,21 @@ jobs:
|
|||||||
echo "CLANG_VERSION=$CLANG_VERSION" >> $GITHUB_ENV
|
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' || '' }}
|
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
|
- name: Install Android Components
|
||||||
if: matrix.platform.name == 'Android'
|
if: matrix.platform.name == 'Android'
|
||||||
run: |
|
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 "build-tools;33.0.2"
|
||||||
echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "ndk;26.1.10909125"
|
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
|
- name: Install macOS Tools
|
||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS'
|
||||||
@ -197,11 +235,11 @@ jobs:
|
|||||||
# Make use of a test to print OpenGL vendor/renderer/version info to the console
|
# 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" \;
|
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')
|
if: runner.os == 'Windows' && !contains(matrix.platform.name, 'MinGW')
|
||||||
run: cmake --build build --target runtests --config ${{ matrix.type.name == 'Debug' && 'Debug' || 'Release' }}
|
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')
|
if: (runner.os != 'Windows' || contains(matrix.platform.name, 'MinGW')) && !contains(matrix.platform.name, 'iOS') && !contains(matrix.platform.name, 'Android')
|
||||||
run: |
|
run: |
|
||||||
ctest --test-dir build --output-on-failure -C ${{ matrix.type.name == 'Debug' && 'Debug' || 'Release' }} --repeat until-pass:3
|
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
|
gcovr -r $GITHUB_WORKSPACE -x build/coverage.out -s -f 'src/SFML/.*' -f 'include/SFML/.*' ${{ matrix.platform.gcovr_options }} $GITHUB_WORKSPACE
|
||||||
fi
|
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
|
- 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
|
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
|
uses: coverallsapp/github-action@v2
|
||||||
@ -311,7 +390,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
platform:
|
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 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 }
|
- { 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
|
- name: Install Linux Dependencies
|
||||||
if: runner.os == 'Linux'
|
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
|
- 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}}
|
run: cmake --preset dev -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++ -DSFML_BUILD_EXAMPLES=OFF -DSFML_ENABLE_SANITIZERS=ON ${{matrix.platform.flags}}
|
||||||
|
@ -224,9 +224,12 @@ endif()
|
|||||||
sfml_set_option(SFML_INSTALL_PKGCONFIG_FILES ${SFML_INSTALL_PKGCONFIG_DEFAULT} BOOL "TRUE to automatically install pkg-config files so other projects can find SFML")
|
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)
|
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
|
# set pkgconfig install directory
|
||||||
# this could be e.g. macports on mac or msys2 on windows etc.
|
# 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)
|
if(SFML_OS_FREEBSD OR SFML_OS_OPENBSD OR SFML_OS_NETBSD)
|
||||||
set(SFML_PKGCONFIG_DIR "/libdata/pkgconfig")
|
set(SFML_PKGCONFIG_DIR "/libdata/pkgconfig")
|
||||||
@ -465,6 +468,13 @@ endif()
|
|||||||
|
|
||||||
sfml_export_targets()
|
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_NAME_SUMMARY "Simple and Fast Multimedia Library")
|
||||||
set(CPACK_PACKAGE_VENDOR "SFML Team")
|
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}")
|
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)
|
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
|
# 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")
|
sfml_set_option(SFML_BUILD_DOC FALSE BOOL "TRUE to generate the API documentation, FALSE to ignore it")
|
||||||
if(SFML_BUILD_DOC)
|
if(SFML_BUILD_DOC)
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
- Ensure GNUInstallDirs cache vars are included before first used (#2778, #2779)
|
- Ensure GNUInstallDirs cache vars are included before first used (#2778, #2779)
|
||||||
- [macOS] Fix incorrect variable expansion (#2780)
|
- [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
|
### Audio
|
||||||
|
|
||||||
|
@ -368,6 +368,11 @@ function(sfml_add_test target SOURCES DEPENDS)
|
|||||||
# Delay test registration when cross compiling to avoid running crosscompiled app on host OS
|
# Delay test registration when cross compiling to avoid running crosscompiled app on host OS
|
||||||
if(CMAKE_CROSSCOMPILING)
|
if(CMAKE_CROSSCOMPILING)
|
||||||
set(CMAKE_CATCH_DISCOVER_TESTS_DISCOVERY_MODE PRE_TEST)
|
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()
|
endif()
|
||||||
|
|
||||||
# Add the test
|
# Add the test
|
||||||
|
@ -26,7 +26,12 @@ if(SFML_OS_WINDOWS)
|
|||||||
PATHS "C:/Program Files/HTML Help Workshop" "C:/Program Files (x86)/HTML Help Workshop"
|
PATHS "C:/Program Files/HTML Help Workshop" "C:/Program Files (x86)/HTML Help Workshop"
|
||||||
DOC "HTML Help Compiler program")
|
DOC "HTML Help Compiler program")
|
||||||
if(DOXYGEN_HHC_PROGRAM)
|
if(DOXYGEN_HHC_PROGRAM)
|
||||||
set(DOXYGEN_GENERATE_HTMLHELP YES)
|
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()
|
else()
|
||||||
set(DOXYGEN_GENERATE_HTMLHELP NO)
|
set(DOXYGEN_GENERATE_HTMLHELP NO)
|
||||||
endif()
|
endif()
|
||||||
|
245
doc/doxyfile.in
245
doc/doxyfile.in
@ -1,4 +1,4 @@
|
|||||||
# Doxyfile 1.9.6
|
# Doxyfile 1.10.0
|
||||||
|
|
||||||
# This file describes the settings to be used by the documentation system
|
# This file describes the settings to be used by the documentation system
|
||||||
# doxygen (www.doxygen.org) for a project.
|
# doxygen (www.doxygen.org) for a project.
|
||||||
@ -63,6 +63,12 @@ PROJECT_BRIEF =
|
|||||||
|
|
||||||
PROJECT_LOGO =
|
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
|
# 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
|
# 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
|
# entered, it will be relative to the location where doxygen was started. If
|
||||||
@ -380,6 +386,17 @@ MARKDOWN_SUPPORT = YES
|
|||||||
|
|
||||||
TOC_INCLUDE_HEADINGS = 0
|
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
|
# When enabled doxygen tries to link words that correspond to documented
|
||||||
# classes, or namespaces to their corresponding documentation. Such a link can
|
# 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
|
# 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
|
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
|
# Build related configuration options
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
@ -516,7 +541,7 @@ NUM_PROC_THREADS = 1
|
|||||||
# normally produced when WARNINGS is set to YES.
|
# normally produced when WARNINGS is set to YES.
|
||||||
# The default value is: NO.
|
# 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
|
# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
|
||||||
# be included in the documentation.
|
# 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
|
# 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
|
# 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.
|
# 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.
|
# The default value is: NO.
|
||||||
|
|
||||||
WARN_AS_ERROR = 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
|
# Note the list of default checked file patterns might differ from the list of
|
||||||
# default file extension mappings.
|
# default file extension mappings.
|
||||||
#
|
#
|
||||||
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
|
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm,
|
||||||
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
|
# *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl,
|
||||||
# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
|
# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d,
|
||||||
# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
|
# *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to
|
||||||
# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
|
# be provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
|
||||||
# *.vhdl, *.ucf, *.qsf and *.ice.
|
# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice.
|
||||||
|
|
||||||
FILE_PATTERNS = *.hpp
|
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
|
# output. The symbol name can be a fully qualified name, a word, or if the
|
||||||
# wildcard * is used, a substring. Examples: ANamespace, AClass,
|
# wildcard * is used, a substring. Examples: ANamespace, AClass,
|
||||||
# ANamespace::AClass, ANamespace::*Test
|
# 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
|
EXCLUDE_SYMBOLS = priv
|
||||||
|
|
||||||
@ -1136,7 +1165,8 @@ FORTRAN_COMMENT_AFTER = 72
|
|||||||
SOURCE_BROWSER = YES
|
SOURCE_BROWSER = YES
|
||||||
|
|
||||||
# Setting the INLINE_SOURCES tag to YES will include the body of functions,
|
# 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.
|
# The default value is: NO.
|
||||||
|
|
||||||
INLINE_SOURCES = NO
|
INLINE_SOURCES = NO
|
||||||
@ -1384,7 +1414,7 @@ HTML_COLORSTYLE = LIGHT
|
|||||||
# Minimum value: 0, maximum value: 359, default value: 220.
|
# Minimum value: 0, maximum value: 359, default value: 220.
|
||||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
# 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
|
# 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
|
# 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
|
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
|
# 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
|
# documentation will contain a main index with vertical navigation menus that
|
||||||
# are dynamically created via JavaScript. If disabled, the navigation index will
|
# are dynamically created via JavaScript. If disabled, the navigation index will
|
||||||
@ -1433,6 +1454,33 @@ HTML_DYNAMIC_MENUS = NO
|
|||||||
|
|
||||||
HTML_DYNAMIC_SECTIONS = 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
|
# 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
|
# shown in the various tree structured indices initially; the user can expand
|
||||||
# and collapse entries dynamically later on. Doxygen will expand the tree to
|
# and collapse entries dynamically later on. Doxygen will expand the tree to
|
||||||
@ -1563,6 +1611,16 @@ BINARY_TOC = NO
|
|||||||
|
|
||||||
TOC_EXPAND = 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
|
# 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
|
# 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
|
# 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
|
USE_PDFLATEX = YES
|
||||||
|
|
||||||
# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
|
# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error.
|
||||||
# command to the generated LaTeX files. This will instruct LaTeX to keep running
|
# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch
|
||||||
# if errors occur, instead of asking the user for help.
|
# 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.
|
# The default value is: NO.
|
||||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||||
|
|
||||||
@ -2074,14 +2139,6 @@ LATEX_HIDE_INDICES = NO
|
|||||||
|
|
||||||
LATEX_BIB_STYLE = plain
|
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)
|
# 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,
|
# 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
|
# 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
|
# 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
|
# the structure of the code including all documentation. Note that this feature
|
||||||
# is still experimental and incomplete at the moment.
|
# is still experimental and incomplete at the moment.
|
||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
@ -2258,6 +2315,28 @@ GENERATE_AUTOGEN_DEF = NO
|
|||||||
# Configuration options related to Sqlite3 output
|
# 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
|
# Configuration options related to the Perl module output
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
@ -2405,15 +2484,15 @@ TAGFILES =
|
|||||||
|
|
||||||
GENERATE_TAGFILE = "@DOXYGEN_OUTPUT_DIR@/SFML.tag"
|
GENERATE_TAGFILE = "@DOXYGEN_OUTPUT_DIR@/SFML.tag"
|
||||||
|
|
||||||
# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
|
# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces
|
||||||
# the class index. If set to NO, only the inherited external classes will be
|
# will be listed in the class and namespace index. If set to NO, only the
|
||||||
# listed.
|
# inherited external classes will be listed.
|
||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
|
|
||||||
ALLEXTERNALS = NO
|
ALLEXTERNALS = NO
|
||||||
|
|
||||||
# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
|
# 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.
|
# listed.
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
|
|
||||||
@ -2427,16 +2506,9 @@ EXTERNAL_GROUPS = YES
|
|||||||
EXTERNAL_PAGES = 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
|
# 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.
|
# and usage relations if the target is undocumented or is not a class.
|
||||||
# The default value is: YES.
|
# 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
|
# 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:
|
# 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
|
# Bell Labs. The other options in this section have no effect if this option is
|
||||||
# set to NO
|
# set to NO
|
||||||
# The default value is: NO.
|
# The default value is: NO.
|
||||||
@ -2498,13 +2570,19 @@ DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4"
|
|||||||
|
|
||||||
DOT_FONTPATH =
|
DOT_FONTPATH =
|
||||||
|
|
||||||
# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a
|
# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will
|
||||||
# graph for each documented class showing the direct and indirect inheritance
|
# generate a graph for each documented class showing the direct and indirect
|
||||||
# relations. In case HAVE_DOT is set as well dot will be used to draw the graph,
|
# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and
|
||||||
# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set
|
# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case
|
||||||
# to TEXT the direct and indirect inheritance relations will be shown as texts /
|
# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the
|
||||||
# links.
|
# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used.
|
||||||
# Possible values are: NO, YES, TEXT and GRAPH.
|
# 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.
|
# The default value is: YES.
|
||||||
|
|
||||||
CLASS_GRAPH = 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
|
# 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
|
# graph for each documented class showing the direct and indirect implementation
|
||||||
# dependencies (inheritance, containment, and class references variables) of the
|
# 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.
|
# The default value is: YES.
|
||||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||||
|
|
||||||
COLLABORATION_GRAPH = YES
|
COLLABORATION_GRAPH = YES
|
||||||
|
|
||||||
# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
|
# 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
|
# groups, showing the direct groups dependencies. Explicit enabling a group
|
||||||
# in the manual.
|
# 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.
|
# The default value is: YES.
|
||||||
# This tag requires that the tag HAVE_DOT is set to 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
|
# 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
|
# 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
|
# significantly it will be wrapped across multiple lines. Some heuristics are
|
||||||
# to avoid ugly line breaks.
|
# applied to avoid ugly line breaks.
|
||||||
# Minimum value: 0, maximum value: 1000, default value: 17.
|
# Minimum value: 0, maximum value: 1000, default value: 17.
|
||||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
# 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
|
# 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
|
# YES then doxygen will generate a graph for each documented file showing the
|
||||||
# direct and indirect include dependencies of the file with other documented
|
# 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.
|
# The default value is: YES.
|
||||||
# This tag requires that the tag HAVE_DOT is set to 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
|
# 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
|
# 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
|
# 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.
|
# The default value is: YES.
|
||||||
# This tag requires that the tag HAVE_DOT is set to 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
|
# 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
|
# dependencies a directory has on other directories in a graphical way. The
|
||||||
# dependency relations are determined by the #include relations between 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.
|
# The default value is: YES.
|
||||||
# This tag requires that the tag HAVE_DOT is set to 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
|
# 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
|
# generated by dot. For an explanation of the image formats see the section
|
||||||
# output formats in the documentation of the dot tool (Graphviz (see:
|
# 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
|
# 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
|
# to make the SVG files visible in IE 9+ (other browsers do not have this
|
||||||
# requirement).
|
# requirement).
|
||||||
@ -2682,11 +2774,12 @@ DOT_PATH =
|
|||||||
|
|
||||||
DOTFILE_DIRS =
|
DOTFILE_DIRS =
|
||||||
|
|
||||||
# The MSCFILE_DIRS tag can be used to specify one or more directories that
|
# You can include diagrams made with dia in doxygen documentation. Doxygen will
|
||||||
# contain msc files that are included in the documentation (see the \mscfile
|
# then run dia to produce the diagram and insert it in the documentation. The
|
||||||
# command).
|
# 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
|
# 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
|
# contain dia files that are included in the documentation (see the \diafile
|
||||||
@ -2763,3 +2856,19 @@ GENERATE_LEGEND = YES
|
|||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
|
|
||||||
DOT_CLEANUP = YES
|
DOT_CLEANUP = YES
|
||||||
|
|
||||||
|
# You can define message sequence charts within doxygen comments using the \msc
|
||||||
|
# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will
|
||||||
|
# use a built-in version of mscgen tool to produce the charts. Alternatively,
|
||||||
|
# the MSCGEN_TOOL tag can also specify the name an external tool. For instance,
|
||||||
|
# specifying prog as the value, doxygen will call the tool as prog -T
|
||||||
|
# <outfile_format> -o <outputfile> <inputfile>. The external tool should support
|
||||||
|
# output file formats "png", "eps", "svg", and "ismap".
|
||||||
|
|
||||||
|
MSCGEN_TOOL =
|
||||||
|
|
||||||
|
# The MSCFILE_DIRS tag can be used to specify one or more directories that
|
||||||
|
# contain msc files that are included in the documentation (see the \mscfile
|
||||||
|
# command).
|
||||||
|
|
||||||
|
MSCFILE_DIRS =
|
||||||
|
@ -147,12 +147,53 @@ div.fragment {
|
|||||||
|
|
||||||
font-family: Consolas, "Liberation Mono", Courier, monospace;
|
font-family: Consolas, "Liberation Mono", Courier, monospace;
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
|
position: relative;
|
||||||
padding: 0.5em 1em;
|
padding: 0.5em 1em;
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
border: 1px solid #bbb;
|
border: 1px solid #bbb;
|
||||||
border-radius(5px);
|
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 {
|
div.line {
|
||||||
min-height: 13px;
|
min-height: 13px;
|
||||||
text-wrap: unrestricted;
|
text-wrap: unrestricted;
|
||||||
@ -1254,7 +1295,7 @@ div.contents ul li {
|
|||||||
width: 24px;
|
width: 24px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
background-image:url('folderopen.png');
|
background-image:url('folderopen.svg');
|
||||||
background-position: 0px -4px;
|
background-position: 0px -4px;
|
||||||
background-repeat: repeat-y;
|
background-repeat: repeat-y;
|
||||||
vertical-align:top;
|
vertical-align:top;
|
||||||
@ -1265,7 +1306,7 @@ div.contents ul li {
|
|||||||
width: 24px;
|
width: 24px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
background-image:url('folderclosed.png');
|
background-image:url('folderclosed.svg');
|
||||||
background-position: 0px -4px;
|
background-position: 0px -4px;
|
||||||
background-repeat: repeat-y;
|
background-repeat: repeat-y;
|
||||||
vertical-align:top;
|
vertical-align:top;
|
||||||
@ -1276,7 +1317,7 @@ div.contents ul li {
|
|||||||
width: 24px;
|
width: 24px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
background-image:url('doc.png');
|
background-image:url('doc.svg');
|
||||||
background-position: 0px -4px;
|
background-position: 0px -4px;
|
||||||
background-repeat: repeat-y;
|
background-repeat: repeat-y;
|
||||||
vertical-align:top;
|
vertical-align:top;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="footer-container">
|
<div id="footer-container">
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
SFML is licensed under the terms and conditions of the <a href="https://www.sfml-dev.org/license.php">zlib/png license</a>.<br>
|
SFML is licensed under the terms and conditions of the <a href="https://www.sfml-dev.org/license.php">zlib/png license</a>.<br />
|
||||||
Copyright © Laurent Gomila ::
|
Copyright © Laurent Gomila ::
|
||||||
Documentation generated by <a href="http://www.doxygen.org/" title="doxygen website">doxygen</a> ::
|
Documentation generated by <a href="http://www.doxygen.org/" title="doxygen website">doxygen</a> ::
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,11 +2,12 @@
|
|||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
<head>
|
<head>
|
||||||
<title>SFML - Simple and Fast Multimedia Library</title>
|
<title>SFML - Simple and Fast Multimedia Library</title>
|
||||||
<meta http-equiv="Content-Type" content="text/html;"/>
|
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<link rel="stylesheet" type="text/css" href="doxygen.css" title="default" media="screen,print" />
|
<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="jquery.js"></script>
|
||||||
<script type="text/javascript" src="dynsections.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="search/search.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="searchOverrides.css" />
|
<link rel="stylesheet" type="text/css" href="searchOverrides.css" />
|
||||||
<script type="text/javascript" src="search/searchdata.js"></script>
|
<script type="text/javascript" src="search/searchdata.js"></script>
|
||||||
@ -24,3 +25,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="content">
|
<div id="content">
|
||||||
|
<div>
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include <SFML/Audio.hpp>
|
#include <SFML/Audio.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -18,6 +19,7 @@ namespace
|
|||||||
constexpr auto windowWidth = 800u;
|
constexpr auto windowWidth = 800u;
|
||||||
constexpr auto windowHeight = 600u;
|
constexpr auto windowHeight = 600u;
|
||||||
constexpr auto pi = 3.14159265359f;
|
constexpr auto pi = 3.14159265359f;
|
||||||
|
constexpr auto sqrt2 = 2.0f * 0.707106781186547524401f;
|
||||||
|
|
||||||
std::filesystem::path resourcesDir()
|
std::filesystem::path resourcesDir()
|
||||||
{
|
{
|
||||||
@ -84,10 +86,10 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
// Virtual functions to be implemented in derived effects
|
// Virtual functions to be implemented in derived effects
|
||||||
virtual void onUpdate(float time, float x, float y) = 0;
|
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 onStart() = 0;
|
||||||
virtual void onStop() = 0;
|
virtual void onStop() = 0;
|
||||||
|
|
||||||
virtual void onKey(sf::Keyboard::Key)
|
virtual void onKey(sf::Keyboard::Key)
|
||||||
{
|
{
|
||||||
@ -113,7 +115,7 @@ public:
|
|||||||
|
|
||||||
// Load the music file
|
// Load the music file
|
||||||
if (!m_music.openFromFile(resourcesDir() / "doodle_pop.ogg"))
|
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
|
// Set the music to loop
|
||||||
m_music.setLoop(true);
|
m_music.setLoop(true);
|
||||||
@ -128,7 +130,7 @@ public:
|
|||||||
m_music.setPosition({m_position.x, m_position.y, 0.f});
|
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);
|
auto statesCopy(states);
|
||||||
statesCopy.transform = sf::Transform::Identity;
|
statesCopy.transform = sf::Transform::Identity;
|
||||||
@ -172,7 +174,7 @@ public:
|
|||||||
{
|
{
|
||||||
// Load the music file
|
// Load the music file
|
||||||
if (!m_music.openFromFile(resourcesDir() / "doodle_pop.ogg"))
|
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
|
// Set the music to loop
|
||||||
m_music.setLoop(true);
|
m_music.setLoop(true);
|
||||||
@ -202,7 +204,7 @@ public:
|
|||||||
m_volumeText.setString("Volume: " + std::to_string(m_volume));
|
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_pitchText, states);
|
||||||
target.draw(m_volumeText, states);
|
target.draw(m_volumeText, states);
|
||||||
@ -275,7 +277,7 @@ public:
|
|||||||
|
|
||||||
// Load the music file
|
// Load the music file
|
||||||
if (!m_music.openFromFile(resourcesDir() / "doodle_pop.ogg"))
|
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
|
// Set the music to loop
|
||||||
m_music.setLoop(true);
|
m_music.setLoop(true);
|
||||||
@ -305,7 +307,7 @@ public:
|
|||||||
m_music.setPosition({m_position.x, m_position.y, 0.f});
|
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);
|
auto statesCopy(states);
|
||||||
|
|
||||||
@ -375,7 +377,7 @@ public:
|
|||||||
m_currentFrequency.setString("Frequency: " + std::to_string(m_frequency) + " Hz");
|
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_instruction, states);
|
||||||
target.draw(m_currentType, states);
|
target.draw(m_currentType, states);
|
||||||
@ -547,7 +549,7 @@ public:
|
|||||||
setDopplerFactor(m_factor);
|
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);
|
auto statesCopy(states);
|
||||||
statesCopy.transform = sf::Transform::Identity;
|
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
|
/// Entry point of application
|
||||||
///
|
///
|
||||||
@ -636,19 +1057,25 @@ int main()
|
|||||||
Effect::setFont(font);
|
Effect::setFont(font);
|
||||||
|
|
||||||
// Create the effects
|
// Create the effects
|
||||||
Surround surroundEffect;
|
Surround surroundEffect;
|
||||||
PitchVolume pitchVolumeEffect;
|
PitchVolume pitchVolumeEffect;
|
||||||
Attenuation attenuationEffect;
|
Attenuation attenuationEffect;
|
||||||
Tone toneEffect;
|
Tone toneEffect;
|
||||||
Doppler dopplerEffect;
|
Doppler dopplerEffect;
|
||||||
|
HighPassFilter highPassFilterEffect;
|
||||||
|
LowPassFilter lowPassFilterEffect;
|
||||||
|
Echo echoEffect;
|
||||||
|
Reverb reverbEffect;
|
||||||
|
|
||||||
const std::array<Effect*, 5> effects{
|
const std::array<Effect*, 9> effects{&surroundEffect,
|
||||||
&surroundEffect,
|
&pitchVolumeEffect,
|
||||||
&pitchVolumeEffect,
|
&attenuationEffect,
|
||||||
&attenuationEffect,
|
&toneEffect,
|
||||||
&toneEffect,
|
&dopplerEffect,
|
||||||
&dopplerEffect,
|
&highPassFilterEffect,
|
||||||
};
|
&lowPassFilterEffect,
|
||||||
|
&echoEffect,
|
||||||
|
&reverbEffect};
|
||||||
|
|
||||||
std::size_t current = 0;
|
std::size_t current = 0;
|
||||||
|
|
||||||
|
348
extlibs/headers/stb_image/stb_image.h
vendored
348
extlibs/headers/stb_image/stb_image.h
vendored
@ -1,4 +1,4 @@
|
|||||||
/* stb_image - v2.28 - public domain image loader - http://nothings.org/stb
|
/* stb_image - v2.29 - public domain image loader - http://nothings.org/stb
|
||||||
no warranty implied; use at your own risk
|
no warranty implied; use at your own risk
|
||||||
|
|
||||||
Do this:
|
Do this:
|
||||||
@ -48,6 +48,7 @@ LICENSE
|
|||||||
|
|
||||||
RECENT REVISION HISTORY:
|
RECENT REVISION HISTORY:
|
||||||
|
|
||||||
|
2.29 (2023-05-xx) optimizations
|
||||||
2.28 (2023-01-29) many error fixes, security errors, just tons of stuff
|
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.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes
|
||||||
2.26 (2020-07-13) many minor 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;
|
return a <= INT_MAX - b;
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns 1 if the product of two signed shorts is valid, 0 on overflow.
|
// returns 1 if the product of two ints fits in a signed short, 0 on overflow.
|
||||||
static int stbi__mul2shorts_valid(short a, short b)
|
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 (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
|
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;
|
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
|
// some JPEGs have junk at end, skip over it but if we find what looks
|
||||||
// like a valid marker, resume there
|
// like a valid marker, resume there
|
||||||
while (!stbi__at_eof(j->s)) {
|
while (!stbi__at_eof(j->s)) {
|
||||||
int x = stbi__get8(j->s);
|
stbi_uc x = stbi__get8(j->s);
|
||||||
while (x == 255) { // might be a marker
|
while (x == 0xff) { // might be a marker
|
||||||
if (stbi__at_eof(j->s)) return STBI__MARKER_none;
|
if (stbi__at_eof(j->s)) return STBI__MARKER_none;
|
||||||
x = stbi__get8(j->s);
|
x = stbi__get8(j->s);
|
||||||
if (x != 0x00 && x != 0xff) {
|
if (x != 0x00 && x != 0xff) {
|
||||||
@ -4176,6 +4177,7 @@ typedef struct
|
|||||||
{
|
{
|
||||||
stbi_uc *zbuffer, *zbuffer_end;
|
stbi_uc *zbuffer, *zbuffer_end;
|
||||||
int num_bits;
|
int num_bits;
|
||||||
|
int hit_zeof_once;
|
||||||
stbi__uint32 code_buffer;
|
stbi__uint32 code_buffer;
|
||||||
|
|
||||||
char *zout;
|
char *zout;
|
||||||
@ -4242,9 +4244,20 @@ stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z)
|
|||||||
int b,s;
|
int b,s;
|
||||||
if (a->num_bits < 16) {
|
if (a->num_bits < 16) {
|
||||||
if (stbi__zeof(a)) {
|
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);
|
||||||
}
|
}
|
||||||
stbi__fill_bits(a);
|
|
||||||
}
|
}
|
||||||
b = z->fast[a->code_buffer & STBI__ZFAST_MASK];
|
b = z->fast[a->code_buffer & STBI__ZFAST_MASK];
|
||||||
if (b) {
|
if (b) {
|
||||||
@ -4309,6 +4322,13 @@ static int stbi__parse_huffman_block(stbi__zbuf *a)
|
|||||||
int len,dist;
|
int len,dist;
|
||||||
if (z == 256) {
|
if (z == 256) {
|
||||||
a->zout = zout;
|
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;
|
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
|
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];
|
dist = stbi__zdist_base[z];
|
||||||
if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[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 - 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;
|
if (!stbi__zexpand(a, zout, len)) return 0;
|
||||||
zout = a->zout;
|
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;
|
if (!stbi__parse_zlib_header(a)) return 0;
|
||||||
a->num_bits = 0;
|
a->num_bits = 0;
|
||||||
a->code_buffer = 0;
|
a->code_buffer = 0;
|
||||||
|
a->hit_zeof_once = 0;
|
||||||
do {
|
do {
|
||||||
final = stbi__zreceive(a,1);
|
final = stbi__zreceive(a,1);
|
||||||
type = stbi__zreceive(a,2);
|
type = stbi__zreceive(a,2);
|
||||||
@ -4619,9 +4640,8 @@ enum {
|
|||||||
STBI__F_up=2,
|
STBI__F_up=2,
|
||||||
STBI__F_avg=3,
|
STBI__F_avg=3,
|
||||||
STBI__F_paeth=4,
|
STBI__F_paeth=4,
|
||||||
// synthetic filters used for first scanline to avoid needing a dummy row of 0s
|
// synthetic filter used for first scanline to avoid needing a dummy row of 0s
|
||||||
STBI__F_avg_first,
|
STBI__F_avg_first
|
||||||
STBI__F_paeth_first
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static stbi_uc first_row_filter[5] =
|
static stbi_uc first_row_filter[5] =
|
||||||
@ -4630,29 +4650,56 @@ static stbi_uc first_row_filter[5] =
|
|||||||
STBI__F_sub,
|
STBI__F_sub,
|
||||||
STBI__F_none,
|
STBI__F_none,
|
||||||
STBI__F_avg_first,
|
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)
|
static int stbi__paeth(int a, int b, int c)
|
||||||
{
|
{
|
||||||
int p = a + b - c;
|
// This formulation looks very different from the reference in the PNG spec, but is
|
||||||
int pa = abs(p-a);
|
// actually equivalent and has favorable data dependencies and admits straightforward
|
||||||
int pb = abs(p-b);
|
// generation of branch-free code, which helps performance significantly.
|
||||||
int pc = abs(p-c);
|
int thresh = c*3 - (a + b);
|
||||||
if (pa <= pb && pa <= pc) return a;
|
int lo = a < b ? a : b;
|
||||||
if (pb <= pc) return b;
|
int hi = a < b ? b : a;
|
||||||
return c;
|
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 };
|
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
|
// 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)
|
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)
|
||||||
{
|
{
|
||||||
int bytes = (depth == 16? 2 : 1);
|
int bytes = (depth == 16 ? 2 : 1);
|
||||||
stbi__context *s = a->s;
|
stbi__context *s = a->s;
|
||||||
stbi__uint32 i,j,stride = x*out_n*bytes;
|
stbi__uint32 i,j,stride = x*out_n*bytes;
|
||||||
stbi__uint32 img_len, img_width_bytes;
|
stbi__uint32 img_len, img_width_bytes;
|
||||||
|
stbi_uc *filter_buf;
|
||||||
|
int all_ok = 1;
|
||||||
int k;
|
int k;
|
||||||
int img_n = s->img_n; // copy it into a local for later
|
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
|
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");
|
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");
|
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);
|
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;
|
img_len = (img_width_bytes + 1) * y;
|
||||||
|
|
||||||
// we used to check for exact match between raw_len and img_len on non-interlaced PNGs,
|
// we used to check for exact match between raw_len and img_len on non-interlaced PNGs,
|
||||||
@ -4673,189 +4723,137 @@ 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.
|
// so just check for raw_len < img_len always.
|
||||||
if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG");
|
if (raw_len < img_len) return stbi__err("not enough pixels","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) {
|
||||||
|
filter_bytes = 1;
|
||||||
|
width = img_width_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
for (j=0; j < y; ++j) {
|
for (j=0; j < y; ++j) {
|
||||||
stbi_uc *cur = a->out + stride*j;
|
// cur/prior filter buffers alternate
|
||||||
stbi_uc *prior;
|
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++;
|
int filter = *raw++;
|
||||||
|
|
||||||
if (filter > 4)
|
// check filter type
|
||||||
return stbi__err("invalid filter","Corrupt PNG");
|
if (filter > 4) {
|
||||||
|
all_ok = stbi__err("invalid filter","Corrupt PNG");
|
||||||
if (depth < 8) {
|
break;
|
||||||
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
|
|
||||||
|
|
||||||
// if first row, use special filter that doesn't sample previous row
|
// if first row, use special filter that doesn't sample previous row
|
||||||
if (j == 0) filter = first_row_filter[filter];
|
if (j == 0) filter = first_row_filter[filter];
|
||||||
|
|
||||||
// handle first byte explicitly
|
// perform actual filtering
|
||||||
for (k=0; k < filter_bytes; ++k) {
|
switch (filter) {
|
||||||
switch (filter) {
|
case STBI__F_none:
|
||||||
case STBI__F_none : cur[k] = raw[k]; break;
|
memcpy(cur, raw, nk);
|
||||||
case STBI__F_sub : cur[k] = raw[k]; break;
|
break;
|
||||||
case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break;
|
case STBI__F_sub:
|
||||||
case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break;
|
memcpy(cur, raw, filter_bytes);
|
||||||
case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break;
|
for (k = filter_bytes; k < nk; ++k)
|
||||||
case STBI__F_avg_first : cur[k] = raw[k]; break;
|
cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]);
|
||||||
case STBI__F_paeth_first: cur[k] = raw[k]; break;
|
break;
|
||||||
}
|
case STBI__F_up:
|
||||||
|
for (k = 0; k < nk; ++k)
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (depth == 8) {
|
raw += nk;
|
||||||
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
|
// expand decoded bits in cur to dest, also adding an extra alpha channel if desired
|
||||||
if (depth < 8 || img_n == out_n) {
|
if (depth < 8) {
|
||||||
int nk = (width - 1)*filter_bytes;
|
|
||||||
#define STBI__CASE(f) \
|
|
||||||
case f: \
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
#undef STBI__CASE
|
|
||||||
raw += nk;
|
|
||||||
} 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
|
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;
|
||||||
|
|
||||||
// note that the final byte might overshoot and write more data than desired.
|
// expand bits to bytes first
|
||||||
// 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) {
|
if (depth == 4) {
|
||||||
for (k=x*img_n; k >= 2; k-=2, ++in) {
|
for (i=0; i < nsmp; ++i) {
|
||||||
*cur++ = scale * ((*in >> 4) );
|
if ((i & 1) == 0) inb = *in++;
|
||||||
*cur++ = scale * ((*in ) & 0x0f);
|
*out++ = scale * (inb >> 4);
|
||||||
|
inb <<= 4;
|
||||||
}
|
}
|
||||||
if (k > 0) *cur++ = scale * ((*in >> 4) );
|
|
||||||
} else if (depth == 2) {
|
} else if (depth == 2) {
|
||||||
for (k=x*img_n; k >= 4; k-=4, ++in) {
|
for (i=0; i < nsmp; ++i) {
|
||||||
*cur++ = scale * ((*in >> 6) );
|
if ((i & 3) == 0) inb = *in++;
|
||||||
*cur++ = scale * ((*in >> 4) & 0x03);
|
*out++ = scale * (inb >> 6);
|
||||||
*cur++ = scale * ((*in >> 2) & 0x03);
|
inb <<= 2;
|
||||||
*cur++ = scale * ((*in ) & 0x03);
|
|
||||||
}
|
}
|
||||||
if (k > 0) *cur++ = scale * ((*in >> 6) );
|
} else {
|
||||||
if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03);
|
STBI_ASSERT(depth == 1);
|
||||||
if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03);
|
for (i=0; i < nsmp; ++i) {
|
||||||
} else if (depth == 1) {
|
if ((i & 7) == 0) inb = *in++;
|
||||||
for (k=x*img_n; k >= 8; k-=8, ++in) {
|
*out++ = scale * (inb >> 7);
|
||||||
*cur++ = scale * ((*in >> 7) );
|
inb <<= 1;
|
||||||
*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 values if desired
|
||||||
// insert alpha = 255
|
if (img_n != out_n)
|
||||||
cur = a->out + stride*j;
|
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);
|
||||||
if (img_n == 1) {
|
if (img_n == 1) {
|
||||||
for (q=x-1; q >= 0; --q) {
|
for (i = 0; i < x; ++i, dest16 += 2, cur += 2) {
|
||||||
cur[q*2+1] = 255;
|
dest16[0] = (cur[0] << 8) | cur[1];
|
||||||
cur[q*2+0] = cur[q];
|
dest16[1] = 0xffff;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
STBI_ASSERT(img_n == 3);
|
STBI_ASSERT(img_n == 3);
|
||||||
for (q=x-1; q >= 0; --q) {
|
for (i = 0; i < x; ++i, dest16 += 4, cur += 6) {
|
||||||
cur[q*4+3] = 255;
|
dest16[0] = (cur[0] << 8) | cur[1];
|
||||||
cur[q*4+2] = cur[q*3+2];
|
dest16[1] = (cur[2] << 8) | cur[3];
|
||||||
cur[q*4+1] = cur[q*3+1];
|
dest16[2] = (cur[4] << 8) | cur[5];
|
||||||
cur[q*4+0] = cur[q*3+0];
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,6 +162,17 @@ public:
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
void setPlayingOffset(Time timeOffset);
|
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
|
/// \brief Get the audio buffer attached to the sound
|
||||||
///
|
///
|
||||||
|
@ -63,7 +63,7 @@ public:
|
|||||||
/// This function uses its own thread so that it doesn't block
|
/// This function uses its own thread so that it doesn't block
|
||||||
/// the rest of the program while the capture runs.
|
/// the rest of the program while the capture runs.
|
||||||
/// Please note that only one capture can happen at the same time.
|
/// 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
|
/// the name to the setDevice() method. If none was selected
|
||||||
/// before, the default capture device will be used. You can get a
|
/// before, the default capture device will be used. You can get a
|
||||||
/// list of the names of all available capture devices by calling
|
/// list of the names of all available capture devices by calling
|
||||||
|
@ -32,6 +32,8 @@
|
|||||||
#include <SFML/System/Angle.hpp>
|
#include <SFML/System/Angle.hpp>
|
||||||
#include <SFML/System/Vector3.hpp>
|
#include <SFML/System/Vector3.hpp>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
|
||||||
namespace sf
|
namespace sf
|
||||||
{
|
{
|
||||||
@ -72,6 +74,76 @@ public:
|
|||||||
float outerGain{}; //!< Outer gain
|
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
|
/// \brief Copy constructor
|
||||||
///
|
///
|
||||||
@ -329,6 +401,17 @@ public:
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
void setAttenuation(float attenuation);
|
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
|
/// \brief Get the pitch of the sound
|
||||||
///
|
///
|
||||||
|
@ -194,6 +194,17 @@ public:
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
bool getLoop() const;
|
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:
|
protected:
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
/// \brief Default constructor
|
/// \brief Default constructor
|
||||||
|
@ -100,11 +100,15 @@ public:
|
|||||||
/// Before calling this function, the render-texture is in
|
/// Before calling this function, the render-texture is in
|
||||||
/// an invalid state, thus it is mandatory to call it before
|
/// an invalid state, thus it is mandatory to call it before
|
||||||
/// doing anything with the render-texture.
|
/// doing anything with the render-texture.
|
||||||
|
///
|
||||||
/// The last parameter, \a settings, is useful if you want to enable
|
/// The last parameter, \a settings, is useful if you want to enable
|
||||||
/// multi-sampling or use the render-texture for OpenGL rendering that
|
/// multi-sampling or use the render-texture for OpenGL rendering that
|
||||||
/// requires a depth or stencil buffer. Otherwise it is unnecessary, and
|
/// requires a depth or stencil buffer. Otherwise it is unnecessary, and
|
||||||
/// you should leave this parameter at its default value.
|
/// 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 size Width and height of the render-texture
|
||||||
/// \param settings Additional settings for the underlying OpenGL texture and context
|
/// \param settings Additional settings for the underlying OpenGL texture and context
|
||||||
///
|
///
|
||||||
|
@ -261,7 +261,7 @@ public:
|
|||||||
/// the \a area rectangle, and to contain 32-bits RGBA pixels.
|
/// the \a area rectangle, and to contain 32-bits RGBA pixels.
|
||||||
///
|
///
|
||||||
/// No additional check is performed on the size of the pixel
|
/// 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.
|
/// behavior.
|
||||||
///
|
///
|
||||||
/// This function does nothing if \a pixels is null or if the
|
/// 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.
|
/// \a height arguments, and it must contain 32-bits RGBA pixels.
|
||||||
///
|
///
|
||||||
/// No additional check is performed on the size of the pixel
|
/// 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.
|
/// arguments will lead to an undefined behavior.
|
||||||
///
|
///
|
||||||
/// This function does nothing if \a pixels is null or if the
|
/// 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,
|
/// Although the source texture can be smaller than this texture,
|
||||||
/// this function is usually used for updating the whole texture.
|
/// this function is usually used for updating the whole texture.
|
||||||
/// The other overload, which has (x, y) additional arguments,
|
/// The other overload, which has an additional destination
|
||||||
/// is more convenient for updating a sub-area of this texture.
|
/// argument, is more convenient for updating a sub-area of this
|
||||||
|
/// texture.
|
||||||
///
|
///
|
||||||
/// No additional check is performed on the size of the passed
|
/// 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.
|
/// will lead to an undefined behavior.
|
||||||
///
|
///
|
||||||
/// This function does nothing if either texture was not
|
/// This function does nothing if either texture was not
|
||||||
@ -315,8 +316,8 @@ public:
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
/// \brief Update a part of this texture from another texture
|
/// \brief Update a part of this texture from another texture
|
||||||
///
|
///
|
||||||
/// No additional check is performed on the size of the texture,
|
/// No additional check is performed on the size of the texture.
|
||||||
/// passing an invalid combination of texture size and destination
|
/// Passing an invalid combination of texture size and destination
|
||||||
/// will lead to an undefined behavior.
|
/// will lead to an undefined behavior.
|
||||||
///
|
///
|
||||||
/// This function does nothing if either texture was not
|
/// This function does nothing if either texture was not
|
||||||
@ -333,11 +334,12 @@ public:
|
|||||||
///
|
///
|
||||||
/// Although the source image can be smaller than the texture,
|
/// Although the source image can be smaller than the texture,
|
||||||
/// this function is usually used for updating the whole texture.
|
/// this function is usually used for updating the whole texture.
|
||||||
/// The other overload, which has (x, y) additional arguments,
|
/// The other overload, which has an additional destination
|
||||||
/// is more convenient for updating a sub-area of the texture.
|
/// argument, is more convenient for updating a sub-area of the
|
||||||
|
/// texture.
|
||||||
///
|
///
|
||||||
/// No additional check is performed on the size of the image,
|
/// No additional check is performed on the size of the image.
|
||||||
/// passing an image bigger than the texture will lead to an
|
/// Passing an image bigger than the texture will lead to an
|
||||||
/// undefined behavior.
|
/// undefined behavior.
|
||||||
///
|
///
|
||||||
/// This function does nothing if the texture was not
|
/// This function does nothing if the texture was not
|
||||||
@ -351,8 +353,8 @@ public:
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
/// \brief Update a part of the texture from an image
|
/// \brief Update a part of the texture from an image
|
||||||
///
|
///
|
||||||
/// No additional check is performed on the size of the image,
|
/// No additional check is performed on the size of the image.
|
||||||
/// passing an invalid combination of image size and destination
|
/// Passing an invalid combination of image size and destination
|
||||||
/// will lead to an undefined behavior.
|
/// will lead to an undefined behavior.
|
||||||
///
|
///
|
||||||
/// This function does nothing if the texture was not
|
/// This function does nothing if the texture was not
|
||||||
@ -369,11 +371,12 @@ public:
|
|||||||
///
|
///
|
||||||
/// Although the source window can be smaller than the texture,
|
/// Although the source window can be smaller than the texture,
|
||||||
/// this function is usually used for updating the whole texture.
|
/// this function is usually used for updating the whole texture.
|
||||||
/// The other overload, which has (x, y) additional arguments,
|
/// The other overload, which has an additional destination
|
||||||
/// is more convenient for updating a sub-area of the texture.
|
/// argument, is more convenient for updating a sub-area of the
|
||||||
|
/// texture.
|
||||||
///
|
///
|
||||||
/// No additional check is performed on the size of the window,
|
/// No additional check is performed on the size of the window.
|
||||||
/// passing a window bigger than the texture will lead to an
|
/// Passing a window bigger than the texture will lead to an
|
||||||
/// undefined behavior.
|
/// undefined behavior.
|
||||||
///
|
///
|
||||||
/// This function does nothing if either the texture or the window
|
/// 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
|
/// \brief Update a part of the texture from the contents of a window
|
||||||
///
|
///
|
||||||
/// No additional check is performed on the size of the window,
|
/// No additional check is performed on the size of the window.
|
||||||
/// passing an invalid combination of window size and destination
|
/// Passing an invalid combination of window size and destination
|
||||||
/// will lead to an undefined behavior.
|
/// will lead to an undefined behavior.
|
||||||
///
|
///
|
||||||
/// This function does nothing if either the texture or the window
|
/// This function does nothing if either the texture or the window
|
||||||
|
@ -33,6 +33,8 @@
|
|||||||
|
|
||||||
#include <SFML/System/Vector2.hpp>
|
#include <SFML/System/Vector2.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
|
||||||
namespace sf
|
namespace sf
|
||||||
{
|
{
|
||||||
@ -267,10 +269,10 @@ private:
|
|||||||
// Member data
|
// Member data
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// clang-format off
|
// 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, 1.f, 0.f, 0.f,
|
||||||
0.f, 0.f, 1.f, 0.f,
|
0.f, 0.f, 1.f, 0.f,
|
||||||
0.f, 0.f, 0.f, 1.f}; //!< 4x4 matrix defining the transformation
|
0.f, 0.f, 0.f, 1.f}; //!< 4x4 matrix defining the transformation
|
||||||
// clang-format off
|
// clang-format off
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ constexpr Transform::Transform(float a00, float a01, float a02,
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
constexpr const float* Transform::getMatrix() const
|
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)
|
constexpr Transform& Transform::combine(const Transform& transform)
|
||||||
{
|
{
|
||||||
const float* a = m_matrix;
|
const auto& a = m_matrix;
|
||||||
const float* b = transform.m_matrix;
|
const auto& b = transform.m_matrix;
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
*this = Transform(a[0] * b[0] + a[4] * b[1] + a[12] * b[3],
|
*this = Transform(a[0] * b[0] + a[4] * b[1] + a[12] * b[3],
|
||||||
|
@ -154,7 +154,7 @@ public:
|
|||||||
/// the \a created buffer.
|
/// the \a created buffer.
|
||||||
///
|
///
|
||||||
/// No additional check is performed on the size of the vertex
|
/// 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.
|
/// behavior.
|
||||||
///
|
///
|
||||||
/// This function does nothing if \a vertices is null or if the
|
/// 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.
|
/// than the size of the currently created buffer, the update fails.
|
||||||
///
|
///
|
||||||
/// No additional check is performed on the size of the vertex
|
/// 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.
|
/// behavior.
|
||||||
///
|
///
|
||||||
/// \param vertices Array of vertices to copy to the buffer
|
/// \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
|
/// Simultaneous updates to the vertex buffer are not guaranteed to be
|
||||||
/// carried out by the driver in any specific order. Updating the same
|
/// carried out by the driver in any specific order. Updating the same
|
||||||
/// region of the buffer from multiple threads will not cause undefined
|
/// 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
|
/// Simultaneous updates of distinct non-overlapping regions of the buffer
|
||||||
/// are also not guaranteed to complete in a specific order. However, in
|
/// are also not guaranteed to complete in a specific order. However, in
|
||||||
|
@ -144,20 +144,20 @@ private:
|
|||||||
friend constexpr Angle radians(float angle);
|
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,
|
/// This function is internal. To construct angle values,
|
||||||
/// use sf::radians or sf::degrees instead.
|
/// 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
|
// 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
|
/// \relates Angle
|
||||||
/// \brief Overload of == operator to compare two angle values
|
/// \brief Overload of == operator to compare two angle values
|
||||||
|
/// \note Does not automatically wrap the angle value
|
||||||
///
|
///
|
||||||
/// \param left Left operand (an angle)
|
/// \param left Left operand (an angle)
|
||||||
/// \param right Right operand (an angle)
|
/// \param right Right operand (an angle)
|
||||||
@ -199,6 +200,7 @@ private:
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
/// \relates Angle
|
/// \relates Angle
|
||||||
/// \brief Overload of != operator to compare two angle values
|
/// \brief Overload of != operator to compare two angle values
|
||||||
|
/// \note Does not automatically wrap the angle value
|
||||||
///
|
///
|
||||||
/// \param left Left operand (an angle)
|
/// \param left Left operand (an angle)
|
||||||
/// \param right Right operand (an angle)
|
/// \param right Right operand (an angle)
|
||||||
@ -211,6 +213,7 @@ private:
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
/// \relates Angle
|
/// \relates Angle
|
||||||
/// \brief Overload of < operator to compare two angle values
|
/// \brief Overload of < operator to compare two angle values
|
||||||
|
/// \note Does not automatically wrap the angle value
|
||||||
///
|
///
|
||||||
/// \param left Left operand (an angle)
|
/// \param left Left operand (an angle)
|
||||||
/// \param right Right operand (an angle)
|
/// \param right Right operand (an angle)
|
||||||
@ -223,6 +226,7 @@ private:
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
/// \relates Angle
|
/// \relates Angle
|
||||||
/// \brief Overload of > operator to compare two angle values
|
/// \brief Overload of > operator to compare two angle values
|
||||||
|
/// \note Does not automatically wrap the angle value
|
||||||
///
|
///
|
||||||
/// \param left Left operand (an angle)
|
/// \param left Left operand (an angle)
|
||||||
/// \param right Right operand (an angle)
|
/// \param right Right operand (an angle)
|
||||||
@ -235,6 +239,7 @@ private:
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
/// \relates Angle
|
/// \relates Angle
|
||||||
/// \brief Overload of <= operator to compare two angle values
|
/// \brief Overload of <= operator to compare two angle values
|
||||||
|
/// \note Does not automatically wrap the angle value
|
||||||
///
|
///
|
||||||
/// \param left Left operand (an angle)
|
/// \param left Left operand (an angle)
|
||||||
/// \param right Right operand (an angle)
|
/// \param right Right operand (an angle)
|
||||||
@ -247,6 +252,7 @@ private:
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
/// \relates Angle
|
/// \relates Angle
|
||||||
/// \brief Overload of >= operator to compare two angle values
|
/// \brief Overload of >= operator to compare two angle values
|
||||||
|
/// \note Does not automatically wrap the angle value
|
||||||
///
|
///
|
||||||
/// \param left Left operand (an angle)
|
/// \param left Left operand (an angle)
|
||||||
/// \param right Right operand (an angle)
|
/// \param right Right operand (an angle)
|
||||||
|
@ -34,16 +34,14 @@ namespace sf
|
|||||||
{
|
{
|
||||||
namespace priv
|
namespace priv
|
||||||
{
|
{
|
||||||
constexpr float pi = 3.141592654f;
|
constexpr float pi = 3.141592654f;
|
||||||
|
constexpr float tau = pi * 2.f;
|
||||||
|
|
||||||
constexpr float positiveRemainder(float a, float b)
|
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;
|
const float val = a - static_cast<float>(static_cast<int>(a / b)) * b;
|
||||||
if (val >= 0.f)
|
return val >= 0.f ? val : val + b;
|
||||||
return val;
|
|
||||||
else
|
|
||||||
return val + b;
|
|
||||||
}
|
}
|
||||||
} // namespace priv
|
} // namespace priv
|
||||||
|
|
||||||
@ -55,33 +53,33 @@ constexpr Angle::Angle() = default;
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
constexpr float Angle::asDegrees() const
|
constexpr float Angle::asDegrees() const
|
||||||
{
|
{
|
||||||
return m_degrees;
|
return m_radians * (180.f / priv::pi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
constexpr float Angle::asRadians() const
|
constexpr float Angle::asRadians() const
|
||||||
{
|
{
|
||||||
return m_degrees * (priv::pi / 180);
|
return m_radians;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
constexpr Angle Angle::wrapSigned() const
|
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
|
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)
|
constexpr Angle degrees(float angle)
|
||||||
{
|
{
|
||||||
return Angle(angle);
|
return Angle(angle * (priv::pi / 180.f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
constexpr Angle radians(float angle)
|
constexpr Angle radians(float angle)
|
||||||
{
|
{
|
||||||
return Angle(angle * (180 / priv::pi));
|
return Angle(angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
constexpr bool operator==(Angle left, Angle right)
|
constexpr bool operator==(Angle left, Angle right)
|
||||||
{
|
{
|
||||||
return left.asDegrees() == right.asDegrees();
|
return left.asRadians() == right.asRadians();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
constexpr bool operator!=(Angle left, Angle right)
|
constexpr bool operator!=(Angle left, Angle right)
|
||||||
{
|
{
|
||||||
return left.asDegrees() != right.asDegrees();
|
return left.asRadians() != right.asRadians();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
constexpr bool operator<(Angle left, Angle right)
|
constexpr bool operator<(Angle left, Angle right)
|
||||||
{
|
{
|
||||||
return left.asDegrees() < right.asDegrees();
|
return left.asRadians() < right.asRadians();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
constexpr bool operator>(Angle left, Angle right)
|
constexpr bool operator>(Angle left, Angle right)
|
||||||
{
|
{
|
||||||
return left.asDegrees() > right.asDegrees();
|
return left.asRadians() > right.asRadians();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
constexpr bool operator<=(Angle left, Angle right)
|
constexpr bool operator<=(Angle left, Angle right)
|
||||||
{
|
{
|
||||||
return left.asDegrees() <= right.asDegrees();
|
return left.asRadians() <= right.asRadians();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
constexpr bool operator>=(Angle left, Angle right)
|
constexpr bool operator>=(Angle left, Angle right)
|
||||||
{
|
{
|
||||||
return left.asDegrees() >= right.asDegrees();
|
return left.asRadians() >= right.asRadians();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
constexpr Angle operator-(Angle right)
|
constexpr Angle operator-(Angle right)
|
||||||
{
|
{
|
||||||
return degrees(-right.asDegrees());
|
return radians(-right.asRadians());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -166,7 +164,7 @@ constexpr Angle& operator+=(Angle& left, Angle right)
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
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)
|
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)
|
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 degrees(left.asDegrees() / right);
|
return radians(left.asRadians() / right);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
constexpr Angle& operator/=(Angle& left, float 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;
|
return left = left / right;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,23 +215,23 @@ constexpr Angle& operator/=(Angle& left, float right)
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
constexpr float operator/(Angle left, Angle right)
|
constexpr float operator/(Angle left, Angle right)
|
||||||
{
|
{
|
||||||
assert(right.asDegrees() != 0 && "Angle::operator/ cannot divide by 0");
|
assert(right.asRadians() != 0.f && "Angle::operator/ cannot divide by 0");
|
||||||
return left.asDegrees() / right.asDegrees();
|
return left.asRadians() / right.asRadians();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
constexpr Angle operator%(Angle left, Angle right)
|
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 degrees(priv::positiveRemainder(left.asDegrees(), right.asDegrees()));
|
return radians(priv::positiveRemainder(left.asRadians(), right.asRadians()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
constexpr Angle& operator%=(Angle& left, Angle right)
|
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;
|
return left = left % right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,8 +147,8 @@ private:
|
|||||||
// Member data
|
// Member data
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
#ifdef SFML_SYSTEM_ANDROID
|
#ifdef SFML_SYSTEM_ANDROID
|
||||||
std::unique_ptr<priv::ResourceStream> m_file;
|
std::unique_ptr<priv::ResourceStream> m_androidFile;
|
||||||
#else
|
#endif
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
/// \brief Deleter for stdio file stream that closes the file stream
|
/// \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
|
std::unique_ptr<std::FILE, FileCloser> m_file; //!< stdio file stream
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sf
|
} // namespace sf
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
#include <SFML/Config.hpp>
|
#include <SFML/Config.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <locale>
|
#include <locale>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
@ -56,7 +56,7 @@ In Utf<8>::decode(In begin, In end, std::uint32_t& output, std::uint32_t replace
|
|||||||
{
|
{
|
||||||
// clang-format off
|
// clang-format off
|
||||||
// Some useful precomputed data
|
// 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,
|
||||||
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
|
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
|
0x00000000, 0x00003080, 0x000E2080, 0x03C82080, 0xFA082080, 0x82082080
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
// decode the character
|
// 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))
|
if (trailingBytes < std::distance(begin, end))
|
||||||
{
|
{
|
||||||
output = 0;
|
output = 0;
|
||||||
@ -110,7 +110,7 @@ template <typename Out>
|
|||||||
Out Utf<8>::encode(std::uint32_t input, Out output, std::uint8_t replacement)
|
Out Utf<8>::encode(std::uint32_t input, Out output, std::uint8_t replacement)
|
||||||
{
|
{
|
||||||
// Some useful precomputed data
|
// 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
|
// encode the character
|
||||||
if ((input > 0x0010FFFF) || ((input >= 0xD800) && (input <= 0xDBFF)))
|
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
|
// clang-format on
|
||||||
|
|
||||||
// Extract the bytes to write
|
// Extract the bytes to write
|
||||||
std::byte bytes[4];
|
std::array<std::byte, 4> bytes{};
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
switch (bytestoWrite)
|
switch (bytestoWrite)
|
||||||
@ -147,7 +147,7 @@ Out Utf<8>::encode(std::uint32_t input, Out output, std::uint8_t replacement)
|
|||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
// Add them to the output
|
// Add them to the output
|
||||||
output = priv::copy(bytes, bytes + bytestoWrite, output);
|
output = priv::copy(bytes.data(), bytes.data() + bytestoWrite, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
|
@ -240,7 +240,7 @@ private:
|
|||||||
/// with either loadFromPixels() or loadFromSystem(), the
|
/// with either loadFromPixels() or loadFromSystem(), the
|
||||||
/// cursor can be changed with sf::WindowBase::setMouseCursor().
|
/// 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.
|
/// in use by the window.
|
||||||
///
|
///
|
||||||
/// Usage example:
|
/// Usage example:
|
||||||
|
@ -329,10 +329,10 @@ private:
|
|||||||
static constexpr bool isEventType = isInParameterPack<T>(decltype(m_data)());
|
static constexpr bool isEventType = isInParameterPack<T>(decltype(m_data)());
|
||||||
};
|
};
|
||||||
|
|
||||||
#include <SFML/Window/Event.inl>
|
|
||||||
|
|
||||||
} // namespace sf
|
} // namespace sf
|
||||||
|
|
||||||
|
#include <SFML/Window/Event.inl>
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
/// \class sf::Event
|
/// \class sf::Event
|
||||||
|
@ -30,6 +30,14 @@
|
|||||||
// to compile the code within the compiletime conditional when
|
// to compile the code within the compiletime conditional when
|
||||||
// an incorrect template parameter is provided.
|
// an incorrect template parameter is provided.
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
// Headers
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
#include <SFML/Window/Event.hpp> // NOLINT(misc-header-include-cycle)
|
||||||
|
|
||||||
|
|
||||||
|
namespace sf
|
||||||
|
{
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
template <typename T>
|
template <typename T>
|
||||||
Event::Event(const T& t)
|
Event::Event(const T& t)
|
||||||
@ -58,3 +66,5 @@ const T* Event::getIf() const
|
|||||||
if constexpr (isEventType<T>)
|
if constexpr (isEventType<T>)
|
||||||
return std::get_if<T>(&m_data);
|
return std::get_if<T>(&m_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace sf
|
||||||
|
@ -44,11 +44,11 @@ namespace sf::Sensor
|
|||||||
enum class Type
|
enum class Type
|
||||||
{
|
{
|
||||||
Accelerometer, //!< Measures the raw acceleration (m/s^2)
|
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)
|
Magnetometer, //!< Measures the ambient magnetic field (micro-teslas)
|
||||||
Gravity, //!< Measures the direction and intensity of gravity, independent of device acceleration (m/s^2)
|
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)
|
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)
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||||
|
@ -30,8 +30,12 @@
|
|||||||
#include <SFML/System/Err.hpp>
|
#include <SFML/System/Err.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
|
||||||
namespace sf::priv
|
namespace sf::priv
|
||||||
{
|
{
|
||||||
@ -64,33 +68,52 @@ AudioDevice::AudioDevice()
|
|||||||
// Create the context
|
// Create the context
|
||||||
m_context.emplace();
|
m_context.emplace();
|
||||||
|
|
||||||
auto contextConfig = ma_context_config_init();
|
auto contextConfig = ma_context_config_init();
|
||||||
contextConfig.pLog = &*m_log;
|
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)
|
||||||
{
|
{
|
||||||
m_context.reset();
|
// We can set backendCount to 1 since it is ignored when backends is set to nullptr
|
||||||
err() << "Failed to initialize the audio context: " << ma_result_description(result) << std::endl;
|
if (const auto result = ma_context_init(backendList, 1, &contextConfig, &*m_context); result != MA_SUCCESS)
|
||||||
return;
|
{
|
||||||
|
m_context.reset();
|
||||||
|
err() << "Failed to initialize the audio playback context: " << ma_result_description(result) << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count the playback devices
|
||||||
|
if (const auto result = ma_context_get_devices(&*m_context, nullptr, &deviceCount, nullptr, nullptr);
|
||||||
|
result != MA_SUCCESS)
|
||||||
|
{
|
||||||
|
err() << "Failed to get audio playback devices: " << ma_result_description(result) << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count the playback devices
|
// If the NULL audio backend also doesn't provide a device we give up
|
||||||
ma_uint32 deviceCount = 0;
|
|
||||||
|
|
||||||
if (const auto result = ma_context_get_devices(&*m_context, nullptr, &deviceCount, nullptr, nullptr);
|
|
||||||
result != MA_SUCCESS)
|
|
||||||
{
|
|
||||||
err() << "Failed to get audio playback devices: " << ma_result_description(result) << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if there are audio playback devices available on the system
|
|
||||||
if (deviceCount == 0)
|
if (deviceCount == 0)
|
||||||
{
|
{
|
||||||
err() << "No audio playback devices available on the system" << std::endl;
|
m_context.reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_context->backend == ma_backend_null)
|
||||||
|
err() << "Using NULL audio backend for playback" << std::endl;
|
||||||
|
|
||||||
// Create the playback device
|
// Create the playback device
|
||||||
m_playbackDevice.emplace();
|
m_playbackDevice.emplace();
|
||||||
|
|
||||||
@ -159,11 +182,16 @@ AudioDevice::AudioDevice()
|
|||||||
m_listenerProperties.upVector.x,
|
m_listenerProperties.upVector.x,
|
||||||
m_listenerProperties.upVector.y,
|
m_listenerProperties.upVector.y,
|
||||||
m_listenerProperties.upVector.z);
|
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
|
// Destroy the engine
|
||||||
if (m_engine)
|
if (m_engine)
|
||||||
@ -286,8 +314,8 @@ void AudioDevice::setCone(const Listener::Cone& cone)
|
|||||||
|
|
||||||
ma_engine_listener_set_cone(&*m_engine,
|
ma_engine_listener_set_cone(&*m_engine,
|
||||||
0,
|
0,
|
||||||
std::clamp(cone.innerAngle.asRadians(), 0.f, sf::degrees(360).asRadians()),
|
std::clamp(cone.innerAngle, Angle::Zero, degrees(360.f)).asRadians(),
|
||||||
std::clamp(cone.outerAngle.asRadians(), 0.f, sf::degrees(360).asRadians()),
|
std::clamp(cone.outerAngle, Angle::Zero, degrees(360.f)).asRadians(),
|
||||||
cone.outerGain);
|
cone.outerGain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,10 +54,10 @@ private:
|
|||||||
AudioDevice();
|
AudioDevice();
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
/// \brief Destructor
|
/// \brief Cleanup function, called via `atexit`
|
||||||
///
|
///
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
~AudioDevice();
|
void cleanup();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
@ -227,11 +227,11 @@ private:
|
|||||||
struct ListenerProperties
|
struct ListenerProperties
|
||||||
{
|
{
|
||||||
float volume{100.f};
|
float volume{100.f};
|
||||||
sf::Vector3f position{0, 0, 0};
|
Vector3f position{0, 0, 0};
|
||||||
sf::Vector3f direction{0, 0, -1};
|
Vector3f direction{0, 0, -1};
|
||||||
sf::Vector3f velocity{0, 0, 0};
|
Vector3f velocity{0, 0, 0};
|
||||||
Listener::Cone cone{sf::degrees(360), sf::degrees(360), 1};
|
Listener::Cone cone{degrees(360.f), degrees(360.f), 1};
|
||||||
sf::Vector3f upVector{0, 1, 0};
|
Vector3f upVector{0, 1, 0};
|
||||||
};
|
};
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
|
@ -82,7 +82,7 @@ sfml_add_library(Audio
|
|||||||
target_compile_definitions(sfml-audio PRIVATE OV_EXCLUDE_STATIC_CALLBACKS FLAC__NO_DLL)
|
target_compile_definitions(sfml-audio PRIVATE OV_EXCLUDE_STATIC_CALLBACKS FLAC__NO_DLL)
|
||||||
|
|
||||||
# disable miniaudio features we do not use
|
# 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
|
# setup dependencies
|
||||||
target_link_libraries(sfml-audio
|
target_link_libraries(sfml-audio
|
||||||
|
@ -63,8 +63,8 @@ struct SavedSettings
|
|||||||
float minGain{0.f};
|
float minGain{0.f};
|
||||||
float maxGain{1.f};
|
float maxGain{1.f};
|
||||||
float rollOff{1.f};
|
float rollOff{1.f};
|
||||||
float innerAngle{sf::degrees(360).asRadians()};
|
float innerAngle{degrees(360.f).asRadians()};
|
||||||
float outerAngle{sf::degrees(360).asRadians()};
|
float outerAngle{degrees(360.f).asRadians()};
|
||||||
float outerGain{0.f};
|
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)
|
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)
|
Time MiniaudioUtils::getPlayingOffset(ma_sound& sound)
|
||||||
{
|
{
|
||||||
|
@ -41,17 +41,18 @@
|
|||||||
namespace sf
|
namespace sf
|
||||||
{
|
{
|
||||||
class Time;
|
class Time;
|
||||||
}
|
|
||||||
|
|
||||||
namespace sf::priv::MiniaudioUtils
|
namespace priv::MiniaudioUtils
|
||||||
{
|
{
|
||||||
[[nodiscard]] ma_channel soundChannelToMiniaudioChannel(sf::SoundChannel soundChannel);
|
[[nodiscard]] ma_channel soundChannelToMiniaudioChannel(SoundChannel soundChannel);
|
||||||
[[nodiscard]] Time getPlayingOffset(ma_sound& sound);
|
[[nodiscard]] SoundChannel miniaudioChannelToSoundChannel(ma_channel soundChannel);
|
||||||
[[nodiscard]] ma_uint64 getFrameIndex(ma_sound& sound, Time timeOffset);
|
[[nodiscard]] Time getPlayingOffset(ma_sound& sound);
|
||||||
|
[[nodiscard]] ma_uint64 getFrameIndex(ma_sound& sound, Time timeOffset);
|
||||||
|
|
||||||
void reinitializeSound(ma_sound& sound, const std::function<void()>& initializeFn);
|
void reinitializeSound(ma_sound& sound, const std::function<void()>& initializeFn);
|
||||||
void initializeSound(const ma_data_source_vtable& vtable,
|
void initializeSound(const ma_data_source_vtable& vtable,
|
||||||
ma_data_source_base& dataSourceBase,
|
ma_data_source_base& dataSourceBase,
|
||||||
ma_sound& sound,
|
ma_sound& sound,
|
||||||
const std::function<void()>& initializeFn);
|
const std::function<void()>& initializeFn);
|
||||||
} // namespace sf::priv::MiniaudioUtils
|
} // namespace priv::MiniaudioUtils
|
||||||
|
} // namespace sf
|
||||||
|
@ -55,6 +55,7 @@ struct Sound::Impl
|
|||||||
~Impl()
|
~Impl()
|
||||||
{
|
{
|
||||||
ma_sound_uninit(&sound);
|
ma_sound_uninit(&sound);
|
||||||
|
ma_node_uninit(&effectNode, nullptr);
|
||||||
ma_data_source_uninit(&dataSourceBase);
|
ma_data_source_uninit(&dataSourceBase);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,6 +91,34 @@ struct Sound::Impl
|
|||||||
return;
|
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
|
// Because we are providing a custom data source, we have to provide the channel map ourselves
|
||||||
if (buffer && !buffer->getChannelMap().empty())
|
if (buffer && !buffer->getChannelMap().empty())
|
||||||
{
|
{
|
||||||
@ -110,7 +139,80 @@ struct Sound::Impl
|
|||||||
|
|
||||||
void reinitialize()
|
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)
|
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
|
// 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_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)
|
std::vector<ma_channel> soundChannelMap; //!< The map of position in sample frame to sound channel (miniaudio channels)
|
||||||
ma_sound sound{}; //!< The sound
|
ma_sound sound{}; //!< The sound
|
||||||
std::size_t cursor{}; //!< The current playing position
|
std::size_t cursor{}; //!< The current playing position
|
||||||
bool looping{}; //!< True if we are looping the sound
|
bool looping{}; //!< True if we are looping the sound
|
||||||
const SoundBuffer* buffer{}; //!< Sound buffer bound to the source
|
const SoundBuffer* buffer{}; //!< Sound buffer bound to the source
|
||||||
Status status{Status::Stopped}; //!< The status
|
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
|
const SoundBuffer& Sound::getBuffer() const
|
||||||
{
|
{
|
||||||
|
@ -69,7 +69,7 @@ public:
|
|||||||
/// \return Properties of the loaded sound if the file was successfully opened
|
/// \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
|
/// \brief Change the current read position to the given sample offset
|
||||||
|
@ -142,16 +142,16 @@ std::optional<SoundFileReader::Info> SoundFileReaderMp3::open(InputStream& strea
|
|||||||
switch (info.channelCount)
|
switch (info.channelCount)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
sf::err() << "No channels in MP3 file" << std::endl;
|
err() << "No channels in MP3 file" << std::endl;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
info.channelMap = {sf::SoundChannel::Mono};
|
info.channelMap = {SoundChannel::Mono};
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
info.channelMap = {sf::SoundChannel::SideLeft, sf::SoundChannel::SideRight};
|
info.channelMap = {SoundChannel::SideLeft, SoundChannel::SideRight};
|
||||||
break;
|
break;
|
||||||
default:
|
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);
|
assert(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -25,82 +25,64 @@
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// Headers
|
// Headers
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
|
#include <SFML/Audio/MiniaudioUtils.hpp>
|
||||||
#include <SFML/Audio/SoundFileReaderWav.hpp>
|
#include <SFML/Audio/SoundFileReaderWav.hpp>
|
||||||
|
|
||||||
#include <SFML/System/Err.hpp>
|
#include <SFML/System/Err.hpp>
|
||||||
#include <SFML/System/InputStream.hpp>
|
#include <SFML/System/InputStream.hpp>
|
||||||
#include <SFML/System/Utils.hpp>
|
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
// The following functions read integers as little endian and
|
ma_result onRead(ma_decoder* decoder, void* buffer, size_t bytesToRead, size_t* bytesRead)
|
||||||
// return them in the host byte order
|
|
||||||
|
|
||||||
bool decode(sf::InputStream& stream, std::uint8_t& value)
|
|
||||||
{
|
{
|
||||||
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)];
|
auto* stream = static_cast<sf::InputStream*>(decoder->pUserData);
|
||||||
if (static_cast<std::size_t>(stream.read(bytes, static_cast<std::int64_t>(sizeof(bytes)))) != sizeof(bytes))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
value = sf::toInteger<std::int16_t>(bytes[0], bytes[1]);
|
switch (origin)
|
||||||
|
{
|
||||||
|
case ma_seek_origin_start:
|
||||||
|
{
|
||||||
|
if (stream->seek(byteOffset) < 0)
|
||||||
|
return MA_ERROR;
|
||||||
|
|
||||||
return true;
|
return MA_SUCCESS;
|
||||||
|
}
|
||||||
|
case ma_seek_origin_current:
|
||||||
|
{
|
||||||
|
const auto currentPosition = stream->tell();
|
||||||
|
|
||||||
|
if (currentPosition < 0)
|
||||||
|
return MA_ERROR;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool decode(sf::InputStream& stream, std::uint16_t& value)
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
std::byte bytes[3];
|
|
||||||
if (static_cast<std::size_t>(stream.read(bytes, static_cast<std::int64_t>(sizeof(bytes)))) != sizeof(bytes))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
value = sf::toInteger<std::uint32_t>(bytes[0], bytes[1], bytes[2]);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool decode(sf::InputStream& stream, std::uint32_t& value)
|
|
||||||
{
|
|
||||||
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::uint32_t>(bytes[0], bytes[1], bytes[2], bytes[3]);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
namespace sf::priv
|
namespace sf::priv
|
||||||
@ -108,331 +90,114 @@ namespace sf::priv
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
bool SoundFileReaderWav::check(InputStream& stream)
|
bool SoundFileReaderWav::check(InputStream& stream)
|
||||||
{
|
{
|
||||||
char header[mainChunkSize];
|
auto config = ma_decoder_config_init_default();
|
||||||
if (stream.read(header, sizeof(header)) < static_cast<std::int64_t>(sizeof(header)))
|
config.encodingFormat = ma_encoding_format_wav;
|
||||||
return false;
|
config.format = ma_format_s16;
|
||||||
|
ma_decoder decoder{};
|
||||||
|
|
||||||
return (header[0] == 'R') && (header[1] == 'I') && (header[2] == 'F') && (header[3] == 'F') && (header[8] == 'W') &&
|
if (ma_decoder_init(&onRead, &onSeek, &stream, &config, &decoder) == MA_SUCCESS)
|
||||||
(header[9] == 'A') && (header[10] == 'V') && (header[11] == 'E');
|
{
|
||||||
|
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)
|
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();
|
auto config = ma_decoder_config_init_default();
|
||||||
if (!info)
|
config.encodingFormat = ma_encoding_format_wav;
|
||||||
err() << "Failed to open WAV sound file (invalid or unsupported file)" << std::endl;
|
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)
|
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))
|
if (const ma_result result = ma_decoder_seek_to_pcm_frame(&*m_decoder, sampleOffset / m_channelCount);
|
||||||
err() << "Failed to seek WAV sound stream" << std::endl;
|
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)
|
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;
|
ma_uint64 framesRead{};
|
||||||
const auto startPos = static_cast<std::uint64_t>(m_stream->tell());
|
|
||||||
|
|
||||||
// Tracking of m_dataEnd is important to prevent sf::Music from reading
|
if (const ma_result result = ma_decoder_read_pcm_frames(&*m_decoder, samples, maxCount / m_channelCount, &framesRead);
|
||||||
// data until EOF, as WAV files may have metadata at the end.
|
result != MA_SUCCESS)
|
||||||
while ((count < maxCount) && (startPos + count * m_bytesPerSample < m_dataEnd))
|
err() << "Failed to read from wav sound stream: " << ma_result_description(result) << std::endl;
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 2:
|
return framesRead * m_channelCount;
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace sf::priv
|
} // namespace sf::priv
|
||||||
|
@ -29,6 +29,8 @@
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
#include <SFML/Audio/SoundFileReader.hpp>
|
#include <SFML/Audio/SoundFileReader.hpp>
|
||||||
|
|
||||||
|
#include <miniaudio.h>
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@ -58,6 +60,12 @@ public:
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
[[nodiscard]] static bool check(InputStream& stream);
|
[[nodiscard]] static bool check(InputStream& stream);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Destructor
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
~SoundFileReaderWav() override;
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
/// \brief Open a sound file for reading
|
/// \brief Open a sound file for reading
|
||||||
///
|
///
|
||||||
@ -66,7 +74,7 @@ public:
|
|||||||
/// \return Properties of the loaded sound if the file was successfully opened
|
/// \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
|
/// \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;
|
[[nodiscard]] std::uint64_t read(std::int16_t* samples, std::uint64_t maxCount) override;
|
||||||
|
|
||||||
private:
|
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
|
// Member data
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
InputStream* m_stream{}; //!< Source stream to read from
|
std::optional<ma_decoder> m_decoder; //!< wav decoder
|
||||||
unsigned int m_bytesPerSample{}; //!< Size of a sample, in bytes
|
ma_uint32 m_channelCount{}; //!< Number of channels
|
||||||
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
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sf::priv
|
} // namespace sf::priv
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
#include <vorbis/vorbisenc.h>
|
#include <vorbis/vorbisenc.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
@ -103,12 +104,12 @@ private:
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// Member data
|
// Member data
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
unsigned int m_channelCount{}; //!< Channel count of the sound being written
|
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
|
std::ofstream m_file; //!< Output file
|
||||||
ogg_stream_state m_ogg{}; //!< OGG stream
|
ogg_stream_state m_ogg{}; //!< OGG stream
|
||||||
vorbis_info m_vorbis{}; //!< Vorbis handle
|
vorbis_info m_vorbis{}; //!< Vorbis handle
|
||||||
vorbis_dsp_state m_state{}; //!< Current encoding state
|
vorbis_dsp_state m_state{}; //!< Current encoding state
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sf::priv
|
} // namespace sf::priv
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
#include <SFML/System/Utils.hpp>
|
#include <SFML/System/Utils.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
@ -45,25 +46,25 @@ namespace
|
|||||||
|
|
||||||
void encode(std::ostream& stream, std::int16_t value)
|
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)};
|
const std::array bytes = {static_cast<char>(value & 0xFF), static_cast<char>(value >> 8)};
|
||||||
stream.write(reinterpret_cast<const char*>(bytes), sizeof(bytes));
|
stream.write(bytes.data(), bytes.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void encode(std::ostream& stream, std::uint16_t value)
|
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)};
|
const std::array bytes = {static_cast<char>(value & 0xFF), static_cast<char>(value >> 8)};
|
||||||
stream.write(reinterpret_cast<const char*>(bytes), sizeof(bytes));
|
stream.write(bytes.data(), bytes.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void encode(std::ostream& stream, std::uint32_t value)
|
void encode(std::ostream& stream, std::uint32_t value)
|
||||||
{
|
{
|
||||||
const std::byte bytes[] = {
|
const std::array bytes = {
|
||||||
static_cast<std::byte>(value & 0x000000FF),
|
static_cast<char>(value & 0x000000FF),
|
||||||
static_cast<std::byte>((value & 0x0000FF00) >> 8),
|
static_cast<char>((value & 0x0000FF00) >> 8),
|
||||||
static_cast<std::byte>((value & 0x00FF0000) >> 16),
|
static_cast<char>((value & 0x00FF0000) >> 16),
|
||||||
static_cast<std::byte>((value & 0xFF000000) >> 24),
|
static_cast<char>((value & 0xFF000000) >> 24),
|
||||||
};
|
};
|
||||||
stream.write(reinterpret_cast<const char*>(bytes), sizeof(bytes));
|
stream.write(bytes.data(), bytes.size());
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@ -247,17 +248,17 @@ void SoundFileWriterWav::writeHeader(unsigned int sampleRate, unsigned int chann
|
|||||||
assert(m_file.good() && "Most recent I/O operation failed");
|
assert(m_file.good() && "Most recent I/O operation failed");
|
||||||
|
|
||||||
// Write the main chunk ID
|
// Write the main chunk ID
|
||||||
char mainChunkId[4] = {'R', 'I', 'F', 'F'};
|
std::array mainChunkId = {'R', 'I', 'F', 'F'};
|
||||||
m_file.write(mainChunkId, sizeof(mainChunkId));
|
m_file.write(mainChunkId.data(), mainChunkId.size());
|
||||||
|
|
||||||
// Write the main chunk header
|
// Write the main chunk header
|
||||||
encode(m_file, static_cast<std::uint32_t>(0)); // 0 is a placeholder, will be written later
|
encode(m_file, static_cast<std::uint32_t>(0)); // 0 is a placeholder, will be written later
|
||||||
char mainChunkFormat[4] = {'W', 'A', 'V', 'E'};
|
std::array mainChunkFormat = {'W', 'A', 'V', 'E'};
|
||||||
m_file.write(mainChunkFormat, sizeof(mainChunkFormat));
|
m_file.write(mainChunkFormat.data(), mainChunkFormat.size());
|
||||||
|
|
||||||
// Write the sub-chunk 1 ("format") id and size
|
// Write the sub-chunk 1 ("format") id and size
|
||||||
char fmtChunkId[4] = {'f', 'm', 't', ' '};
|
std::array fmtChunkId = {'f', 'm', 't', ' '};
|
||||||
m_file.write(fmtChunkId, sizeof(fmtChunkId));
|
m_file.write(fmtChunkId.data(), fmtChunkId.size());
|
||||||
|
|
||||||
if (channelCount > 2)
|
if (channelCount > 2)
|
||||||
{
|
{
|
||||||
@ -295,14 +296,14 @@ void SoundFileWriterWav::writeHeader(unsigned int sampleRate, unsigned int chann
|
|||||||
encode(m_file, bitsPerSample);
|
encode(m_file, bitsPerSample);
|
||||||
encode(m_file, channelMask);
|
encode(m_file, channelMask);
|
||||||
// Write the subformat (PCM)
|
// 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'};
|
{'\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
|
// Write the sub-chunk 2 ("data") id and size
|
||||||
char dataChunkId[4] = {'d', 'a', 't', 'a'};
|
std::array dataChunkId = {'d', 'a', 't', 'a'};
|
||||||
m_file.write(dataChunkId, sizeof(dataChunkId));
|
m_file.write(dataChunkId.data(), dataChunkId.size());
|
||||||
const std::uint32_t dataChunkSize = 0; // placeholder, will be written later
|
const std::uint32_t dataChunkSize = 0; // placeholder, will be written later
|
||||||
encode(m_file, dataChunkSize);
|
encode(m_file, dataChunkSize);
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
#include <SFML/Audio/SoundFileWriter.hpp>
|
#include <SFML/Audio/SoundFileWriter.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
@ -105,9 +106,9 @@ private:
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// Member data
|
// Member data
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
std::ofstream m_file; //!< File stream to write to
|
std::ofstream m_file; //!< File stream to write to
|
||||||
unsigned int m_channelCount{}; //!< Channel count of the sound being written
|
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
|
} // namespace sf::priv
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
#include <miniaudio.h>
|
#include <miniaudio.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
|
||||||
@ -187,16 +188,52 @@ SoundRecorder::SoundRecorder() : m_impl(std::make_unique<Impl>(this))
|
|||||||
// Create the context
|
// Create the context
|
||||||
m_impl->context.emplace();
|
m_impl->context.emplace();
|
||||||
|
|
||||||
auto contextConfig = ma_context_config_init();
|
auto contextConfig = ma_context_config_init();
|
||||||
contextConfig.pLog = &*m_impl->log;
|
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 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();
|
m_impl->context.reset();
|
||||||
err() << "Failed to initialize the audio context: " << ma_result_description(result) << std::endl;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_impl->context->backend == ma_backend_null)
|
||||||
|
err() << "Using NULL audio backend for capture" << std::endl;
|
||||||
|
|
||||||
// Create the capture device
|
// Create the capture device
|
||||||
m_impl->initialize();
|
m_impl->initialize();
|
||||||
}
|
}
|
||||||
|
@ -88,8 +88,8 @@ void SoundSource::setCone(const Cone& cone)
|
|||||||
{
|
{
|
||||||
if (auto* sound = static_cast<ma_sound*>(getSound()))
|
if (auto* sound = static_cast<ma_sound*>(getSound()))
|
||||||
ma_sound_set_cone(sound,
|
ma_sound_set_cone(sound,
|
||||||
std::clamp(cone.innerAngle, sf::degrees(0), sf::degrees(360)).asRadians(),
|
std::clamp(cone.innerAngle, Angle::Zero, degrees(360.f)).asRadians(),
|
||||||
std::clamp(cone.outerAngle, sf::degrees(0), sf::degrees(360)).asRadians(),
|
std::clamp(cone.outerAngle, Angle::Zero, degrees(360.f)).asRadians(),
|
||||||
cone.outerGain);
|
cone.outerGain);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,6 +166,13 @@ void SoundSource::setAttenuation(float attenuation)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
// NOLINTNEXTLINE(performance-unnecessary-value-param)
|
||||||
|
void SoundSource::setEffectProcessor(EffectProcessor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
float SoundSource::getPitch() const
|
float SoundSource::getPitch() const
|
||||||
{
|
{
|
||||||
@ -241,12 +248,12 @@ SoundSource::Cone SoundSource::getCone() const
|
|||||||
float outerAngle = 0.f;
|
float outerAngle = 0.f;
|
||||||
Cone cone;
|
Cone cone;
|
||||||
ma_sound_get_cone(sound, &innerAngle, &outerAngle, &cone.outerGain);
|
ma_sound_get_cone(sound, &innerAngle, &outerAngle, &cone.outerGain);
|
||||||
cone.innerAngle = sf::radians(innerAngle);
|
cone.innerAngle = radians(innerAngle);
|
||||||
cone.outerAngle = sf::radians(outerAngle);
|
cone.outerAngle = radians(outerAngle);
|
||||||
return cone;
|
return cone;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Cone{sf::radians(0), sf::radians(0), 0.f};
|
return Cone{radians(0), radians(0), 0.f};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ struct SoundStream::Impl
|
|||||||
~Impl()
|
~Impl()
|
||||||
{
|
{
|
||||||
ma_sound_uninit(&sound);
|
ma_sound_uninit(&sound);
|
||||||
|
ma_node_uninit(&effectNode, nullptr);
|
||||||
ma_data_source_uninit(&dataSourceBase);
|
ma_data_source_uninit(&dataSourceBase);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,6 +92,34 @@ struct SoundStream::Impl
|
|||||||
return;
|
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
|
// Because we are providing a custom data source, we have to provide the channel map ourselves
|
||||||
if (!channelMap.empty())
|
if (!channelMap.empty())
|
||||||
{
|
{
|
||||||
@ -111,7 +140,79 @@ struct SoundStream::Impl
|
|||||||
|
|
||||||
void reinitialize()
|
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)
|
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
|
// 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_data_source_base dataSourceBase{}; //!< The struct that makes this object a miniaudio data source (must be first member)
|
||||||
SoundStream* const owner; //!< Owning SoundStream object
|
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)
|
std::vector<ma_channel> soundChannelMap; //!< The map of position in sample frame to sound channel (miniaudio channels)
|
||||||
ma_sound sound{}; //!< The sound
|
ma_sound sound{}; //!< The sound
|
||||||
std::vector<std::int16_t> sampleBuffer; //!< Our temporary sample buffer
|
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 loop{}; //!< Loop flag (true to loop, false to play once)
|
||||||
bool streaming{true}; //!< True if we are still streaming samples from the source
|
bool streaming{true}; //!< True if we are still streaming samples from the source
|
||||||
Status status{Status::Stopped}; //!< The status
|
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()
|
std::optional<std::uint64_t> SoundStream::onLoop()
|
||||||
{
|
{
|
||||||
|
@ -71,7 +71,7 @@ std::size_t CircleShape::getPointCount() const
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
Vector2f CircleShape::getPoint(std::size_t index) const
|
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);
|
return Vector2f(m_radius, m_radius) + Vector2f(m_radius, angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -784,7 +784,7 @@ bool Font::setCurrentSize(unsigned int characterSize) const
|
|||||||
Font::Page::Page(bool smooth)
|
Font::Page::Page(bool smooth)
|
||||||
{
|
{
|
||||||
// Make sure that the texture is initialized by default
|
// Make sure that the texture is initialized by default
|
||||||
sf::Image image;
|
Image image;
|
||||||
image.create({128, 128}, Color::Transparent);
|
image.create({128, 128}, Color::Transparent);
|
||||||
|
|
||||||
// Reserve a 2x2 white square for texturing underlines
|
// Reserve a 2x2 white square for texturing underlines
|
||||||
|
@ -61,9 +61,9 @@ void ensureExtensionsInit()
|
|||||||
initialized = true;
|
initialized = true;
|
||||||
|
|
||||||
#ifdef SFML_OPENGL_ES
|
#ifdef SFML_OPENGL_ES
|
||||||
gladLoadGLES1(sf::Context::getFunction);
|
gladLoadGLES1(Context::getFunction);
|
||||||
#else
|
#else
|
||||||
gladLoadGL(sf::Context::getFunction);
|
gladLoadGL(Context::getFunction);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Retrieve the context version number
|
// Retrieve the context version number
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
#include <SFML/System/InputStream.hpp>
|
#include <SFML/System/InputStream.hpp>
|
||||||
#include <SFML/System/Utils.hpp>
|
#include <SFML/System/Utils.hpp>
|
||||||
#ifdef SFML_SYSTEM_ANDROID
|
#ifdef SFML_SYSTEM_ANDROID
|
||||||
|
#include <SFML/System/Android/Activity.hpp>
|
||||||
#include <SFML/System/Android/ResourceStream.hpp>
|
#include <SFML/System/Android/ResourceStream.hpp>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -157,7 +158,15 @@ void Image::create(const Vector2u& size, const std::uint8_t* pixels)
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
bool Image::loadFromFile(const std::filesystem::path& filename)
|
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)
|
// Clear the array (just in case)
|
||||||
m_pixels.clear();
|
m_pixels.clear();
|
||||||
@ -186,13 +195,6 @@ bool Image::loadFromFile(const std::filesystem::path& filename)
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
priv::ResourceStream stream(filename);
|
|
||||||
return loadFromStream(stream);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -740,7 +740,7 @@ bool Shader::isAvailable()
|
|||||||
const TransientContextLock contextLock;
|
const TransientContextLock contextLock;
|
||||||
|
|
||||||
// Make sure that extensions are initialized
|
// Make sure that extensions are initialized
|
||||||
sf::priv::ensureExtensionsInit();
|
priv::ensureExtensionsInit();
|
||||||
|
|
||||||
return GLEXT_multitexture && GLEXT_shading_language_100 && GLEXT_shader_objects && GLEXT_vertex_shader &&
|
return GLEXT_multitexture && GLEXT_shading_language_100 && GLEXT_shader_objects && GLEXT_vertex_shader &&
|
||||||
GLEXT_fragment_shader;
|
GLEXT_fragment_shader;
|
||||||
@ -758,7 +758,7 @@ bool Shader::isGeometryAvailable()
|
|||||||
const TransientContextLock contextLock;
|
const TransientContextLock contextLock;
|
||||||
|
|
||||||
// Make sure that extensions are initialized
|
// Make sure that extensions are initialized
|
||||||
sf::priv::ensureExtensionsInit();
|
priv::ensureExtensionsInit();
|
||||||
|
|
||||||
return isAvailable() && (GLEXT_geometry_shader4 || GLEXT_GL_VERSION_3_2);
|
return isAvailable() && (GLEXT_geometry_shader4 || GLEXT_GL_VERSION_3_2);
|
||||||
}();
|
}();
|
||||||
|
@ -381,7 +381,7 @@ void Text::ensureGeometryUpdate() const
|
|||||||
const bool isBold = m_style & Bold;
|
const bool isBold = m_style & Bold;
|
||||||
const bool isUnderlined = m_style & Underlined;
|
const bool isUnderlined = m_style & Underlined;
|
||||||
const bool isStrikeThrough = m_style & StrikeThrough;
|
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 underlineOffset = m_font->getUnderlinePosition(m_characterSize);
|
||||||
const float underlineThickness = m_font->getUnderlineThickness(m_characterSize);
|
const float underlineThickness = m_font->getUnderlineThickness(m_characterSize);
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
#include <SFML/System/Err.hpp>
|
#include <SFML/System/Err.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@ -861,10 +862,10 @@ void Texture::bind(const Texture* texture, CoordinateType coordinateType)
|
|||||||
if ((coordinateType == CoordinateType::Pixels) || texture->m_pixelsFlipped)
|
if ((coordinateType == CoordinateType::Pixels) || texture->m_pixelsFlipped)
|
||||||
{
|
{
|
||||||
// clang-format off
|
// 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, 1.f, 0.f, 0.f,
|
||||||
0.f, 0.f, 1.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, 1.f};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
// If non-normalized coordinates (= pixels) are requested, we need to
|
// If non-normalized coordinates (= pixels) are requested, we need to
|
||||||
@ -884,7 +885,7 @@ void Texture::bind(const Texture* texture, CoordinateType coordinateType)
|
|||||||
|
|
||||||
// Load the matrix
|
// Load the matrix
|
||||||
glCheck(glMatrixMode(GL_TEXTURE));
|
glCheck(glMatrixMode(GL_TEXTURE));
|
||||||
glCheck(glLoadMatrixf(matrix));
|
glCheck(glLoadMatrixf(matrix.data()));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -921,7 +922,7 @@ unsigned int Texture::getMaximumSize()
|
|||||||
GLint value = 0;
|
GLint value = 0;
|
||||||
|
|
||||||
// Make sure that extensions are initialized
|
// Make sure that extensions are initialized
|
||||||
sf::priv::ensureExtensionsInit();
|
priv::ensureExtensionsInit();
|
||||||
|
|
||||||
glCheck(glGetIntegerv(GL_MAX_TEXTURE_SIZE, &value));
|
glCheck(glGetIntegerv(GL_MAX_TEXTURE_SIZE, &value));
|
||||||
|
|
||||||
|
@ -31,6 +31,8 @@
|
|||||||
#include <SFML/System/String.hpp>
|
#include <SFML/System/String.hpp>
|
||||||
#include <SFML/System/Utils.hpp>
|
#include <SFML/System/Utils.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cwchar>
|
#include <cwchar>
|
||||||
|
|
||||||
@ -212,8 +214,8 @@ Packet& Packet::operator>>(std::uint64_t& data)
|
|||||||
{
|
{
|
||||||
// Since ntohll is not available everywhere, we have to convert
|
// Since ntohll is not available everywhere, we have to convert
|
||||||
// to network byte order (big endian) manually
|
// to network byte order (big endian) manually
|
||||||
std::byte bytes[sizeof(data)];
|
std::array<std::byte, sizeof(data)> bytes{};
|
||||||
std::memcpy(bytes, &m_data[m_readPos], sizeof(data));
|
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]);
|
data = toInteger<std::uint64_t>(bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]);
|
||||||
|
|
||||||
@ -427,14 +429,14 @@ Packet& Packet::operator<<(std::int64_t data)
|
|||||||
// Since htonll is not available everywhere, we have to convert
|
// Since htonll is not available everywhere, we have to convert
|
||||||
// to network byte order (big endian) manually
|
// 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 >> 48) & 0xFF),
|
||||||
static_cast<std::uint8_t>((data >> 40) & 0xFF),
|
static_cast<std::uint8_t>((data >> 40) & 0xFF),
|
||||||
static_cast<std::uint8_t>((data >> 32) & 0xFF),
|
static_cast<std::uint8_t>((data >> 32) & 0xFF),
|
||||||
static_cast<std::uint8_t>((data >> 24) & 0xFF),
|
static_cast<std::uint8_t>((data >> 24) & 0xFF),
|
||||||
static_cast<std::uint8_t>((data >> 16) & 0xFF),
|
static_cast<std::uint8_t>((data >> 16) & 0xFF),
|
||||||
static_cast<std::uint8_t>((data >> 8) & 0xFF),
|
static_cast<std::uint8_t>((data >> 8) & 0xFF),
|
||||||
static_cast<std::uint8_t>((data)&0xFF)};
|
static_cast<std::uint8_t>((data)&0xFF)};
|
||||||
|
|
||||||
append(&toWrite, sizeof(toWrite));
|
append(&toWrite, sizeof(toWrite));
|
||||||
return *this;
|
return *this;
|
||||||
@ -447,14 +449,14 @@ Packet& Packet::operator<<(std::uint64_t data)
|
|||||||
// Since htonll is not available everywhere, we have to convert
|
// Since htonll is not available everywhere, we have to convert
|
||||||
// to network byte order (big endian) manually
|
// 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 >> 48) & 0xFF),
|
||||||
static_cast<std::uint8_t>((data >> 40) & 0xFF),
|
static_cast<std::uint8_t>((data >> 40) & 0xFF),
|
||||||
static_cast<std::uint8_t>((data >> 32) & 0xFF),
|
static_cast<std::uint8_t>((data >> 32) & 0xFF),
|
||||||
static_cast<std::uint8_t>((data >> 24) & 0xFF),
|
static_cast<std::uint8_t>((data >> 24) & 0xFF),
|
||||||
static_cast<std::uint8_t>((data >> 16) & 0xFF),
|
static_cast<std::uint8_t>((data >> 16) & 0xFF),
|
||||||
static_cast<std::uint8_t>((data >> 8) & 0xFF),
|
static_cast<std::uint8_t>((data >> 8) & 0xFF),
|
||||||
static_cast<std::uint8_t>((data)&0xFF)};
|
static_cast<std::uint8_t>((data)&0xFF)};
|
||||||
|
|
||||||
append(&toWrite, sizeof(toWrite));
|
append(&toWrite, sizeof(toWrite));
|
||||||
return *this;
|
return *this;
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
#include <SFML/System/Err.hpp>
|
#include <SFML/System/Err.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@ -401,12 +402,12 @@ Socket::Status TcpSocket::receive(Packet& packet)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Loop until we receive all the packet data
|
// Loop until we receive all the packet data
|
||||||
char buffer[1024];
|
std::array<char, 1024> buffer{};
|
||||||
while (m_pendingPacket.data.size() < packetSize)
|
while (m_pendingPacket.data.size() < packetSize)
|
||||||
{
|
{
|
||||||
// Receive a chunk of data
|
// Receive a chunk of data
|
||||||
const std::size_t sizeToGet = std::min(packetSize - m_pendingPacket.data.size(), sizeof(buffer));
|
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)
|
if (status != Status::Done)
|
||||||
return status;
|
return status;
|
||||||
|
|
||||||
@ -415,7 +416,7 @@ Socket::Status TcpSocket::receive(Packet& packet)
|
|||||||
{
|
{
|
||||||
m_pendingPacket.data.resize(m_pendingPacket.data.size() + received);
|
m_pendingPacket.data.resize(m_pendingPacket.data.size() + received);
|
||||||
std::byte* begin = m_pendingPacket.data.data() + m_pendingPacket.data.size() - received;
|
std::byte* begin = m_pendingPacket.data.data() + m_pendingPacket.data.size() - received;
|
||||||
std::memcpy(begin, buffer, received);
|
std::memcpy(begin, buffer.data(), received);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +45,7 @@
|
|||||||
#include <SFML/System/Vector2.hpp>
|
#include <SFML/System/Vector2.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
#include <SFML/System/FileInputStream.hpp>
|
#include <SFML/System/FileInputStream.hpp>
|
||||||
#ifdef SFML_SYSTEM_ANDROID
|
#ifdef SFML_SYSTEM_ANDROID
|
||||||
|
#include <SFML/System/Android/Activity.hpp>
|
||||||
#include <SFML/System/Android/ResourceStream.hpp>
|
#include <SFML/System/Android/ResourceStream.hpp>
|
||||||
#endif
|
#endif
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -36,12 +37,10 @@
|
|||||||
namespace sf
|
namespace sf
|
||||||
{
|
{
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
#ifndef SFML_SYSTEM_ANDROID
|
|
||||||
void FileInputStream::FileCloser::operator()(std::FILE* file)
|
void FileInputStream::FileCloser::operator()(std::FILE* file)
|
||||||
{
|
{
|
||||||
std::fclose(file);
|
std::fclose(file);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
@ -64,69 +63,88 @@ FileInputStream& FileInputStream::operator=(FileInputStream&&) noexcept = defaul
|
|||||||
bool FileInputStream::open(const std::filesystem::path& filename)
|
bool FileInputStream::open(const std::filesystem::path& filename)
|
||||||
{
|
{
|
||||||
#ifdef SFML_SYSTEM_ANDROID
|
#ifdef SFML_SYSTEM_ANDROID
|
||||||
m_file = std::make_unique<priv::ResourceStream>(filename);
|
if (priv::getActivityStatesPtr() != nullptr)
|
||||||
return m_file->tell() != -1;
|
{
|
||||||
#else
|
m_androidFile = std::make_unique<priv::ResourceStream>(filename);
|
||||||
|
return m_androidFile->tell() != -1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#ifdef SFML_SYSTEM_WINDOWS
|
#ifdef SFML_SYSTEM_WINDOWS
|
||||||
m_file.reset(_wfopen(filename.c_str(), L"rb"));
|
m_file.reset(_wfopen(filename.c_str(), L"rb"));
|
||||||
#else
|
#else
|
||||||
m_file.reset(std::fopen(filename.c_str(), "rb"));
|
m_file.reset(std::fopen(filename.c_str(), "rb"));
|
||||||
#endif
|
#endif
|
||||||
return m_file != nullptr;
|
return m_file != nullptr;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
std::int64_t FileInputStream::read(void* data, std::int64_t size)
|
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)
|
if (!m_file)
|
||||||
return -1;
|
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()));
|
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)
|
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)
|
if (!m_file)
|
||||||
return -1;
|
return -1;
|
||||||
#ifdef SFML_SYSTEM_ANDROID
|
|
||||||
return m_file->seek(position);
|
|
||||||
#else
|
|
||||||
if (std::fseek(m_file.get(), static_cast<long>(position), SEEK_SET))
|
if (std::fseek(m_file.get(), static_cast<long>(position), SEEK_SET))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
return tell();
|
return tell();
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
std::int64_t FileInputStream::tell()
|
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)
|
if (!m_file)
|
||||||
return -1;
|
return -1;
|
||||||
#ifdef SFML_SYSTEM_ANDROID
|
|
||||||
return m_file->tell();
|
|
||||||
#else
|
|
||||||
return std::ftell(m_file.get());
|
return std::ftell(m_file.get());
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
std::int64_t FileInputStream::getSize()
|
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)
|
if (!m_file)
|
||||||
return -1;
|
return -1;
|
||||||
#ifdef SFML_SYSTEM_ANDROID
|
|
||||||
return m_file->getSize();
|
|
||||||
#else
|
|
||||||
const std::int64_t position = tell();
|
const std::int64_t position = tell();
|
||||||
std::fseek(m_file.get(), 0, SEEK_END);
|
std::fseek(m_file.get(), 0, SEEK_END);
|
||||||
const std::int64_t size = tell();
|
const std::int64_t size = tell();
|
||||||
@ -135,7 +153,6 @@ std::int64_t FileInputStream::getSize()
|
|||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
return size;
|
return size;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace sf
|
} // namespace sf
|
||||||
|
@ -264,10 +264,10 @@ std::wstring String::toWideString() const
|
|||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
sf::U8String String::toUtf8() const
|
U8String String::toUtf8() const
|
||||||
{
|
{
|
||||||
// Prepare the output string
|
// Prepare the output string
|
||||||
sf::U8String output;
|
U8String output;
|
||||||
output.reserve(m_string.length());
|
output.reserve(m_string.length());
|
||||||
|
|
||||||
// Convert
|
// Convert
|
||||||
|
@ -38,7 +38,7 @@ namespace sf::priv
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
String ClipboardImpl::getString()
|
String ClipboardImpl::getString()
|
||||||
{
|
{
|
||||||
sf::err() << "Clipboard API not implemented for Android.\n";
|
err() << "Clipboard API not implemented for Android.\n";
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ String ClipboardImpl::getString()
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
void ClipboardImpl::setString(const String& /* text */)
|
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
|
} // namespace sf::priv
|
||||||
|
@ -55,7 +55,7 @@ struct JoystickState
|
|||||||
{
|
{
|
||||||
bool connected{}; //!< Is the joystick currently connected?
|
bool connected{}; //!< Is the joystick currently connected?
|
||||||
EnumArray<Joystick::Axis, float, Joystick::AxisCount> axes{}; //!< Position of each axis, in range [-100, 100]
|
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
|
} // namespace sf::priv
|
||||||
|
@ -30,6 +30,8 @@
|
|||||||
#include <SFML/Window/Joystick.hpp>
|
#include <SFML/Window/Joystick.hpp>
|
||||||
#include <SFML/Window/JoystickImpl.hpp>
|
#include <SFML/Window/JoystickImpl.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
|
||||||
namespace sf::priv
|
namespace sf::priv
|
||||||
{
|
{
|
||||||
@ -124,7 +126,7 @@ private:
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// Member data
|
// 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
|
} // namespace sf::priv
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
|
|
||||||
#include <SFML/System/Err.hpp>
|
#include <SFML/System/Err.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -513,10 +514,10 @@ void GlxContext::createSurface(GlxContext* shared, const Vector2u& size, unsigne
|
|||||||
|
|
||||||
if (config)
|
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};
|
{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);
|
updateSettingsFromVisualInfo(&visualInfo);
|
||||||
|
|
||||||
@ -578,11 +579,11 @@ void GlxContext::createContext(GlxContext* shared)
|
|||||||
|
|
||||||
glXQueryDrawable(m_display.get(), m_pbuffer, GLX_FBCONFIG_ID, &fbConfigId);
|
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;
|
int count = 0;
|
||||||
const auto fbconfig = X11Ptr<GLXFBConfig>(
|
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)
|
if (count == 1)
|
||||||
visualInfo = X11Ptr<XVisualInfo>(glXGetVisualFromFBConfig(m_display.get(), *fbconfig));
|
visualInfo = X11Ptr<XVisualInfo>(glXGetVisualFromFBConfig(m_display.get(), *fbconfig));
|
||||||
|
@ -400,14 +400,13 @@ std::string getJoystickName(unsigned int index)
|
|||||||
if (fd >= 0)
|
if (fd >= 0)
|
||||||
{
|
{
|
||||||
// Get the name
|
// Get the name
|
||||||
char name[128] = {};
|
std::array<char, 128> name{};
|
||||||
|
const int result = ioctl(fd, JSIOCGNAME(name.size()), name.data());
|
||||||
const int result = ioctl(fd, JSIOCGNAME(sizeof(name)), name);
|
|
||||||
|
|
||||||
::close(fd);
|
::close(fd);
|
||||||
|
|
||||||
if (result >= 0)
|
if (result >= 0)
|
||||||
return name;
|
return name.data();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall back to manual USB chain walk via udev
|
// Fall back to manual USB chain walk via udev
|
||||||
@ -600,7 +599,7 @@ JoystickCaps JoystickImpl::getCapabilities() const
|
|||||||
ioctl(m_file, JSIOCGAXES, &axesCount);
|
ioctl(m_file, JSIOCGAXES, &axesCount);
|
||||||
for (int i = 0; i < axesCount; ++i)
|
for (int i = 0; i < axesCount; ++i)
|
||||||
{
|
{
|
||||||
switch (m_mapping[i])
|
switch (m_mapping[static_cast<std::size_t>(i)])
|
||||||
{
|
{
|
||||||
// clang-format off
|
// clang-format off
|
||||||
case ABS_X: caps.axes[Joystick::Axis::X] = true; break;
|
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;
|
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])
|
switch (m_mapping[joyState.number])
|
||||||
{
|
{
|
||||||
|
@ -105,10 +105,10 @@ private:
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// Member data
|
// Member data
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
int m_file{-1}; ///< File descriptor of the joystick
|
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
|
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
|
} // namespace sf::priv
|
||||||
|
@ -138,7 +138,7 @@ bool WindowBase::isOpen() const
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
Event WindowBase::pollEvent()
|
Event WindowBase::pollEvent()
|
||||||
{
|
{
|
||||||
sf::Event event;
|
Event event;
|
||||||
if (m_impl && (event = m_impl->popEvent(false)))
|
if (m_impl && (event = m_impl->popEvent(false)))
|
||||||
filterEvent(event);
|
filterEvent(event);
|
||||||
return event;
|
return event;
|
||||||
@ -148,7 +148,7 @@ Event WindowBase::pollEvent()
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
Event WindowBase::waitEvent()
|
Event WindowBase::waitEvent()
|
||||||
{
|
{
|
||||||
sf::Event event;
|
Event event;
|
||||||
if (m_impl && (event = m_impl->popEvent(true)))
|
if (m_impl && (event = m_impl->popEvent(true)))
|
||||||
filterEvent(event);
|
filterEvent(event);
|
||||||
return event;
|
return event;
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
#include <SFML/System/Sleep.hpp>
|
#include <SFML/System/Sleep.hpp>
|
||||||
#include <SFML/System/Time.hpp>
|
#include <SFML/System/Time.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@ -96,7 +97,7 @@ namespace sf::priv
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
struct WindowImpl::JoystickStatesImpl
|
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
|
// Get the initial sensor states
|
||||||
for (sf::Vector3f& vec : m_sensorValue)
|
for (Vector3f& vec : m_sensorValue)
|
||||||
vec = Vector3f(0, 0, 0);
|
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
|
// Pop the first event of the queue, if it is not empty
|
||||||
if (!m_events.empty())
|
if (!m_events.empty())
|
||||||
|
@ -34,11 +34,6 @@
|
|||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
unsigned int deviceMotionEnabledCount = 0;
|
unsigned int deviceMotionEnabledCount = 0;
|
||||||
|
|
||||||
float toDegrees(float radians)
|
|
||||||
{
|
|
||||||
return sf::radians(radians).asDegrees();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -145,10 +140,10 @@ Vector3f SensorImpl::update()
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case Sensor::Type::Gyroscope:
|
case Sensor::Type::Gyroscope:
|
||||||
// Rotation rates are given in rad/s, convert to deg/s
|
// Rotation rates are given in rad/s
|
||||||
value.x = toDegrees(static_cast<float>(manager.gyroData.rotationRate.x));
|
value.x = static_cast<float>(manager.gyroData.rotationRate.x);
|
||||||
value.y = toDegrees(static_cast<float>(manager.gyroData.rotationRate.y));
|
value.y = static_cast<float>(manager.gyroData.rotationRate.y);
|
||||||
value.z = toDegrees(static_cast<float>(manager.gyroData.rotationRate.z));
|
value.z = static_cast<float>(manager.gyroData.rotationRate.z);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Sensor::Type::Magnetometer:
|
case Sensor::Type::Magnetometer:
|
||||||
@ -166,10 +161,10 @@ Vector3f SensorImpl::update()
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case Sensor::Type::Orientation:
|
case Sensor::Type::Orientation:
|
||||||
// Absolute rotation (Euler) angles are given in radians, convert to degrees
|
// Absolute rotation (Euler) angles are given in radians
|
||||||
value.x = toDegrees(static_cast<float>(manager.deviceMotion.attitude.yaw));
|
value.x = static_cast<float>(manager.deviceMotion.attitude.yaw);
|
||||||
value.y = toDegrees(static_cast<float>(manager.deviceMotion.attitude.pitch));
|
value.y = static_cast<float>(manager.deviceMotion.attitude.pitch);
|
||||||
value.z = toDegrees(static_cast<float>(manager.deviceMotion.attitude.roll));
|
value.z = static_cast<float>(manager.deviceMotion.attitude.roll);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -29,6 +29,8 @@
|
|||||||
#include <SFML/Window/macOS/HIDInputManager.hpp>
|
#include <SFML/Window/macOS/HIDInputManager.hpp>
|
||||||
#include <SFML/Window/macOS/HIDJoystickManager.hpp>
|
#include <SFML/Window/macOS/HIDJoystickManager.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// Private data
|
// Private data
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
@ -75,11 +77,8 @@ HIDJoystickManager::HIDJoystickManager()
|
|||||||
|
|
||||||
CFDictionaryRef mask1 = HIDInputManager::copyDevicesMask(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad);
|
CFDictionaryRef mask1 = HIDInputManager::copyDevicesMask(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad);
|
||||||
|
|
||||||
CFDictionaryRef maskArray[2];
|
std::array maskArray = {mask0, mask1};
|
||||||
maskArray[0] = mask0;
|
CFArrayRef mask = CFArrayCreate(nullptr, reinterpret_cast<const void**>(maskArray.data()), maskArray.size(), nullptr);
|
||||||
maskArray[1] = mask1;
|
|
||||||
|
|
||||||
CFArrayRef mask = CFArrayCreate(nullptr, reinterpret_cast<const void**>(maskArray), 2, nullptr);
|
|
||||||
|
|
||||||
IOHIDManagerSetDeviceMatchingMultiple(m_manager, mask);
|
IOHIDManagerSetDeviceMatchingMultiple(m_manager, mask);
|
||||||
CFRelease(mask);
|
CFRelease(mask);
|
||||||
|
@ -111,7 +111,7 @@ private:
|
|||||||
// Member data
|
// Member data
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
using Location = long;
|
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>;
|
using ButtonsVector = std::vector<IOHIDElementRef>;
|
||||||
|
|
||||||
AxisMap m_axis; ///< Axes (but not POV/Hat) of the joystick
|
AxisMap m_axis; ///< Axes (but not POV/Hat) of the joystick
|
||||||
@ -121,7 +121,7 @@ private:
|
|||||||
Joystick::Identification m_identification; ///< Joystick identification
|
Joystick::Identification m_identification; ///< Joystick identification
|
||||||
|
|
||||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
// 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
|
/// For a corresponding SFML index, m_locationIDs is either some USB
|
||||||
/// location or 0 if there isn't currently a connected joystick device
|
/// location or 0 if there isn't currently a connected joystick device
|
||||||
};
|
};
|
||||||
|
@ -229,7 +229,7 @@ void SFContext::createContext(SFContext* shared, unsigned int bitsPerPixel, cons
|
|||||||
{
|
{
|
||||||
if (!(m_settings.attributeFlags & ContextSettings::Core))
|
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.attributeFlags |= ContextSettings::Core;
|
||||||
}
|
}
|
||||||
m_settings.majorVersion = 3;
|
m_settings.majorVersion = 3;
|
||||||
@ -240,7 +240,7 @@ void SFContext::createContext(SFContext* shared, unsigned int bitsPerPixel, cons
|
|||||||
|
|
||||||
if (m_settings.attributeFlags & ContextSettings::Debug)
|
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);
|
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)
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,7 +267,7 @@ void SFContext::createContext(SFContext* shared, unsigned int bitsPerPixel, cons
|
|||||||
|
|
||||||
if (sharedContext == [NSOpenGLContext currentContext])
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -277,13 +277,13 @@ void SFContext::createContext(SFContext* shared, unsigned int bitsPerPixel, cons
|
|||||||
|
|
||||||
if (m_context == nil)
|
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];
|
m_context = [[NSOpenGLContext alloc] initWithFormat:pixFmt shareContext:nil];
|
||||||
|
|
||||||
if (m_context == 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
|
else
|
||||||
sf::err() << "Warning. New context created without shared context." << std::endl;
|
err() << "Warning. New context created without shared context." << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Free up.
|
// Free up.
|
||||||
|
@ -1,8 +1,209 @@
|
|||||||
#include <SFML/Audio/Music.hpp>
|
#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>
|
#include <type_traits>
|
||||||
|
|
||||||
static_assert(!std::is_copy_constructible_v<sf::Music>);
|
TEST_CASE("[Audio] sf::Music", runAudioDeviceTests())
|
||||||
static_assert(!std::is_copy_assignable_v<sf::Music>);
|
{
|
||||||
static_assert(!std::is_nothrow_move_constructible_v<sf::Music>);
|
SECTION("Type traits")
|
||||||
static_assert(!std::is_nothrow_move_assignable_v<sf::Music>);
|
{
|
||||||
|
STATIC_CHECK(!std::is_copy_constructible_v<sf::Music>);
|
||||||
|
STATIC_CHECK(!std::is_copy_assignable_v<sf::Music>);
|
||||||
|
STATIC_CHECK(!std::is_nothrow_move_constructible_v<sf::Music>);
|
||||||
|
STATIC_CHECK(!std::is_nothrow_move_assignable_v<sf::Music>);
|
||||||
|
STATIC_CHECK(std::has_virtual_destructor_v<sf::Music>);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Span")
|
||||||
|
{
|
||||||
|
const sf::Music::Span<float> span;
|
||||||
|
CHECK(span.offset == 0);
|
||||||
|
CHECK(span.length == 0);
|
||||||
|
|
||||||
|
const sf::Music::TimeSpan timeSpan;
|
||||||
|
CHECK(timeSpan.offset == sf::Time::Zero);
|
||||||
|
CHECK(timeSpan.length == sf::Time::Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Construction")
|
||||||
|
{
|
||||||
|
const sf::Music music;
|
||||||
|
CHECK(music.getDuration() == sf::Time::Zero);
|
||||||
|
const auto [offset, length] = music.getLoopPoints();
|
||||||
|
CHECK(offset == sf::Time::Zero);
|
||||||
|
CHECK(length == sf::Time::Zero);
|
||||||
|
CHECK(music.getChannelCount() == 0);
|
||||||
|
CHECK(music.getSampleRate() == 0);
|
||||||
|
CHECK(music.getStatus() == sf::Music::Status::Stopped);
|
||||||
|
CHECK(music.getPlayingOffset() == sf::Time::Zero);
|
||||||
|
CHECK(!music.getLoop());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("openFromFile()")
|
||||||
|
{
|
||||||
|
sf::Music music;
|
||||||
|
|
||||||
|
SECTION("Invalid file")
|
||||||
|
{
|
||||||
|
REQUIRE(!music.openFromFile("does/not/exist.wav"));
|
||||||
|
CHECK(music.getDuration() == sf::Time::Zero);
|
||||||
|
const auto [offset, length] = music.getLoopPoints();
|
||||||
|
CHECK(offset == sf::Time::Zero);
|
||||||
|
CHECK(length == sf::Time::Zero);
|
||||||
|
CHECK(music.getChannelCount() == 0);
|
||||||
|
CHECK(music.getSampleRate() == 0);
|
||||||
|
CHECK(music.getStatus() == sf::Music::Status::Stopped);
|
||||||
|
CHECK(music.getPlayingOffset() == sf::Time::Zero);
|
||||||
|
CHECK(!music.getLoop());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Valid file")
|
||||||
|
{
|
||||||
|
REQUIRE(music.openFromFile("Audio/ding.mp3"));
|
||||||
|
CHECK(music.getDuration() == sf::microseconds(1990884));
|
||||||
|
const auto [offset, length] = music.getLoopPoints();
|
||||||
|
CHECK(offset == sf::Time::Zero);
|
||||||
|
CHECK(length == sf::microseconds(1990884));
|
||||||
|
CHECK(music.getChannelCount() == 1);
|
||||||
|
CHECK(music.getSampleRate() == 44100);
|
||||||
|
CHECK(music.getStatus() == sf::Music::Status::Stopped);
|
||||||
|
CHECK(music.getPlayingOffset() == sf::Time::Zero);
|
||||||
|
CHECK(!music.getLoop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("openFromMemory()")
|
||||||
|
{
|
||||||
|
std::vector<std::byte> memory;
|
||||||
|
sf::Music music;
|
||||||
|
|
||||||
|
SECTION("Invalid buffer")
|
||||||
|
{
|
||||||
|
REQUIRE(!music.openFromMemory(memory.data(), memory.size()));
|
||||||
|
CHECK(music.getDuration() == sf::Time::Zero);
|
||||||
|
const auto [offset, length] = music.getLoopPoints();
|
||||||
|
CHECK(offset == sf::Time::Zero);
|
||||||
|
CHECK(length == sf::Time::Zero);
|
||||||
|
CHECK(music.getChannelCount() == 0);
|
||||||
|
CHECK(music.getSampleRate() == 0);
|
||||||
|
CHECK(music.getStatus() == sf::Music::Status::Stopped);
|
||||||
|
CHECK(music.getPlayingOffset() == sf::Time::Zero);
|
||||||
|
CHECK(!music.getLoop());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Valid buffer")
|
||||||
|
{
|
||||||
|
memory = loadIntoMemory("Audio/ding.flac");
|
||||||
|
REQUIRE(music.openFromMemory(memory.data(), memory.size()));
|
||||||
|
CHECK(music.getDuration() == sf::microseconds(1990884));
|
||||||
|
const auto [offset, length] = music.getLoopPoints();
|
||||||
|
CHECK(offset == sf::Time::Zero);
|
||||||
|
CHECK(length == sf::microseconds(1990884));
|
||||||
|
CHECK(music.getChannelCount() == 1);
|
||||||
|
CHECK(music.getSampleRate() == 44100);
|
||||||
|
CHECK(music.getStatus() == sf::Music::Status::Stopped);
|
||||||
|
CHECK(music.getPlayingOffset() == sf::Time::Zero);
|
||||||
|
CHECK(!music.getLoop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("openFromStream()")
|
||||||
|
{
|
||||||
|
sf::FileInputStream stream;
|
||||||
|
sf::Music music;
|
||||||
|
|
||||||
|
SECTION("Invalid stream")
|
||||||
|
{
|
||||||
|
CHECK(!music.openFromStream(stream));
|
||||||
|
CHECK(music.getDuration() == sf::Time::Zero);
|
||||||
|
const auto [offset, length] = music.getLoopPoints();
|
||||||
|
CHECK(offset == sf::Time::Zero);
|
||||||
|
CHECK(length == sf::Time::Zero);
|
||||||
|
CHECK(music.getChannelCount() == 0);
|
||||||
|
CHECK(music.getSampleRate() == 0);
|
||||||
|
CHECK(music.getStatus() == sf::Music::Status::Stopped);
|
||||||
|
CHECK(music.getPlayingOffset() == sf::Time::Zero);
|
||||||
|
CHECK(!music.getLoop());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Valid stream")
|
||||||
|
{
|
||||||
|
REQUIRE(stream.open("Audio/doodle_pop.ogg"));
|
||||||
|
REQUIRE(music.openFromStream(stream));
|
||||||
|
CHECK(music.getDuration() == sf::microseconds(24002176));
|
||||||
|
const auto [offset, length] = music.getLoopPoints();
|
||||||
|
CHECK(offset == sf::Time::Zero);
|
||||||
|
CHECK(length == sf::microseconds(24002176));
|
||||||
|
CHECK(music.getChannelCount() == 2);
|
||||||
|
CHECK(music.getSampleRate() == 44100);
|
||||||
|
CHECK(music.getStatus() == sf::Music::Status::Stopped);
|
||||||
|
CHECK(music.getPlayingOffset() == sf::Time::Zero);
|
||||||
|
CHECK(!music.getLoop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("play/pause/stop")
|
||||||
|
{
|
||||||
|
sf::Music music;
|
||||||
|
REQUIRE(music.openFromFile("Audio/ding.mp3"));
|
||||||
|
|
||||||
|
// Wait for background thread to start
|
||||||
|
music.play();
|
||||||
|
while (music.getStatus() == sf::Music::Status::Stopped)
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
CHECK(music.getStatus() == sf::Music::Status::Playing);
|
||||||
|
|
||||||
|
// Wait for background thread to pause
|
||||||
|
music.pause();
|
||||||
|
while (music.getStatus() == sf::Music::Status::Playing)
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
CHECK(music.getStatus() == sf::Music::Status::Paused);
|
||||||
|
|
||||||
|
// Wait for background thread to stop
|
||||||
|
music.stop();
|
||||||
|
while (music.getStatus() == sf::Music::Status::Paused)
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
CHECK(music.getStatus() == sf::Music::Status::Stopped);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("setLoopPoints()")
|
||||||
|
{
|
||||||
|
sf::Music music;
|
||||||
|
|
||||||
|
SECTION("No file")
|
||||||
|
{
|
||||||
|
music.setLoopPoints({sf::Time::Zero, sf::Time::Zero});
|
||||||
|
const auto [offset, length] = music.getLoopPoints();
|
||||||
|
CHECK(offset == sf::Time::Zero);
|
||||||
|
CHECK(length == sf::Time::Zero);
|
||||||
|
CHECK(music.getChannelCount() == 0);
|
||||||
|
CHECK(music.getSampleRate() == 0);
|
||||||
|
CHECK(music.getStatus() == sf::Music::Status::Stopped);
|
||||||
|
CHECK(music.getPlayingOffset() == sf::Time::Zero);
|
||||||
|
CHECK(!music.getLoop());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Loaded file")
|
||||||
|
{
|
||||||
|
REQUIRE(music.openFromFile("Audio/killdeer.wav"));
|
||||||
|
music.setLoopPoints({sf::seconds(1), sf::seconds(2)});
|
||||||
|
const auto [offset, length] = music.getLoopPoints();
|
||||||
|
CHECK(offset == sf::seconds(1));
|
||||||
|
CHECK(length == sf::seconds(2));
|
||||||
|
CHECK(music.getChannelCount() == 1);
|
||||||
|
CHECK(music.getSampleRate() == 22050);
|
||||||
|
CHECK(music.getStatus() == sf::Music::Status::Stopped);
|
||||||
|
CHECK(music.getPlayingOffset() == sf::Time::Zero);
|
||||||
|
CHECK(!music.getLoop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,11 +1,86 @@
|
|||||||
#include <SFML/Audio/Sound.hpp>
|
#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>
|
#include <type_traits>
|
||||||
|
|
||||||
static_assert(!std::is_constructible_v<sf::Sound, sf::SoundBuffer&&>);
|
TEST_CASE("[Audio] sf::Sound", runAudioDeviceTests())
|
||||||
static_assert(std::is_copy_constructible_v<sf::Sound>);
|
{
|
||||||
static_assert(std::is_copy_assignable_v<sf::Sound>);
|
SECTION("Type traits")
|
||||||
static_assert(std::is_move_constructible_v<sf::Sound>);
|
{
|
||||||
static_assert(!std::is_nothrow_move_constructible_v<sf::Sound>);
|
STATIC_CHECK(!std::is_constructible_v<sf::Sound, sf::SoundBuffer&&>);
|
||||||
static_assert(std::is_move_assignable_v<sf::Sound>);
|
STATIC_CHECK(std::is_copy_constructible_v<sf::Sound>);
|
||||||
static_assert(!std::is_nothrow_move_assignable_v<sf::Sound>);
|
STATIC_CHECK(std::is_copy_assignable_v<sf::Sound>);
|
||||||
|
STATIC_CHECK(std::is_move_constructible_v<sf::Sound>);
|
||||||
|
STATIC_CHECK(!std::is_nothrow_move_constructible_v<sf::Sound>);
|
||||||
|
STATIC_CHECK(std::is_move_assignable_v<sf::Sound>);
|
||||||
|
STATIC_CHECK(!std::is_nothrow_move_assignable_v<sf::Sound>);
|
||||||
|
STATIC_CHECK(std::has_virtual_destructor_v<sf::Sound>);
|
||||||
|
}
|
||||||
|
|
||||||
|
sf::SoundBuffer soundBuffer;
|
||||||
|
REQUIRE(soundBuffer.loadFromFile("Audio/ding.flac"));
|
||||||
|
|
||||||
|
SECTION("Construction")
|
||||||
|
{
|
||||||
|
const sf::Sound sound(soundBuffer);
|
||||||
|
CHECK(&sound.getBuffer() == &soundBuffer);
|
||||||
|
CHECK(!sound.getLoop());
|
||||||
|
CHECK(sound.getPlayingOffset() == sf::Time::Zero);
|
||||||
|
CHECK(sound.getStatus() == sf::Sound::Status::Stopped);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Copy semantics")
|
||||||
|
{
|
||||||
|
const sf::Sound sound(soundBuffer);
|
||||||
|
|
||||||
|
SECTION("Construction")
|
||||||
|
{
|
||||||
|
const sf::Sound soundCopy(sound); // NOLINT(performance-unnecessary-copy-initialization)
|
||||||
|
CHECK(&soundCopy.getBuffer() == &soundBuffer);
|
||||||
|
CHECK(!soundCopy.getLoop());
|
||||||
|
CHECK(soundCopy.getPlayingOffset() == sf::Time::Zero);
|
||||||
|
CHECK(soundCopy.getStatus() == sf::Sound::Status::Stopped);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Assignment")
|
||||||
|
{
|
||||||
|
const sf::SoundBuffer emptySoundBuffer;
|
||||||
|
sf::Sound soundCopy(emptySoundBuffer);
|
||||||
|
soundCopy = sound;
|
||||||
|
CHECK(&soundCopy.getBuffer() == &soundBuffer);
|
||||||
|
CHECK(!soundCopy.getLoop());
|
||||||
|
CHECK(soundCopy.getPlayingOffset() == sf::Time::Zero);
|
||||||
|
CHECK(soundCopy.getStatus() == sf::Sound::Status::Stopped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Set/get buffer")
|
||||||
|
{
|
||||||
|
const sf::SoundBuffer otherSoundBuffer;
|
||||||
|
sf::Sound sound(soundBuffer);
|
||||||
|
sound.setBuffer(otherSoundBuffer);
|
||||||
|
CHECK(&sound.getBuffer() == &otherSoundBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Set/get loop")
|
||||||
|
{
|
||||||
|
sf::Sound sound(soundBuffer);
|
||||||
|
sound.setLoop(true);
|
||||||
|
CHECK(sound.getLoop());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Set/get playing offset")
|
||||||
|
{
|
||||||
|
sf::Sound sound(soundBuffer);
|
||||||
|
sound.setPlayingOffset(sf::seconds(10));
|
||||||
|
CHECK(sound.getPlayingOffset() == sf::seconds(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,10 +1,147 @@
|
|||||||
#include <SFML/Audio/SoundBuffer.hpp>
|
#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>
|
#include <type_traits>
|
||||||
|
|
||||||
static_assert(std::is_copy_constructible_v<sf::SoundBuffer>);
|
TEST_CASE("[Audio] sf::SoundBuffer", runAudioDeviceTests())
|
||||||
static_assert(std::is_copy_assignable_v<sf::SoundBuffer>);
|
{
|
||||||
static_assert(std::is_move_constructible_v<sf::SoundBuffer>);
|
SECTION("Type traits")
|
||||||
static_assert(!std::is_nothrow_move_constructible_v<sf::SoundBuffer>);
|
{
|
||||||
static_assert(std::is_move_assignable_v<sf::SoundBuffer>);
|
STATIC_CHECK(std::is_copy_constructible_v<sf::SoundBuffer>);
|
||||||
static_assert(!std::is_nothrow_move_assignable_v<sf::SoundBuffer>);
|
STATIC_CHECK(std::is_copy_assignable_v<sf::SoundBuffer>);
|
||||||
|
STATIC_CHECK(std::is_move_constructible_v<sf::SoundBuffer>);
|
||||||
|
STATIC_CHECK(!std::is_nothrow_move_constructible_v<sf::SoundBuffer>);
|
||||||
|
STATIC_CHECK(std::is_move_assignable_v<sf::SoundBuffer>);
|
||||||
|
STATIC_CHECK(!std::is_nothrow_move_assignable_v<sf::SoundBuffer>);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Construction")
|
||||||
|
{
|
||||||
|
const sf::SoundBuffer soundBuffer;
|
||||||
|
CHECK(soundBuffer.getSamples() == nullptr);
|
||||||
|
CHECK(soundBuffer.getSampleCount() == 0);
|
||||||
|
CHECK(soundBuffer.getSampleRate() == 44100);
|
||||||
|
CHECK(soundBuffer.getChannelCount() == 1);
|
||||||
|
CHECK(soundBuffer.getDuration() == sf::Time::Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Copy semantics")
|
||||||
|
{
|
||||||
|
sf::SoundBuffer soundBuffer;
|
||||||
|
REQUIRE(soundBuffer.loadFromFile("Audio/ding.flac"));
|
||||||
|
|
||||||
|
SECTION("Construction")
|
||||||
|
{
|
||||||
|
const sf::SoundBuffer soundBufferCopy(soundBuffer); // NOLINT(performance-unnecessary-copy-initialization)
|
||||||
|
CHECK(soundBufferCopy.getSamples() != nullptr);
|
||||||
|
CHECK(soundBufferCopy.getSampleCount() == 87798);
|
||||||
|
CHECK(soundBufferCopy.getSampleRate() == 44100);
|
||||||
|
CHECK(soundBufferCopy.getChannelCount() == 1);
|
||||||
|
CHECK(soundBufferCopy.getDuration() == sf::microseconds(1990884));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Assignment")
|
||||||
|
{
|
||||||
|
sf::SoundBuffer soundBufferCopy;
|
||||||
|
soundBufferCopy = soundBuffer;
|
||||||
|
CHECK(soundBufferCopy.getSamples() != nullptr);
|
||||||
|
CHECK(soundBufferCopy.getSampleCount() == 87798);
|
||||||
|
CHECK(soundBufferCopy.getSampleRate() == 44100);
|
||||||
|
CHECK(soundBufferCopy.getChannelCount() == 1);
|
||||||
|
CHECK(soundBufferCopy.getDuration() == sf::microseconds(1990884));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("loadFromFile()")
|
||||||
|
{
|
||||||
|
sf::SoundBuffer soundBuffer;
|
||||||
|
|
||||||
|
SECTION("Invalid filename")
|
||||||
|
{
|
||||||
|
CHECK(!soundBuffer.loadFromFile("does/not/exist.wav"));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Valid file")
|
||||||
|
{
|
||||||
|
REQUIRE(soundBuffer.loadFromFile("Audio/ding.flac"));
|
||||||
|
CHECK(soundBuffer.getSamples() != nullptr);
|
||||||
|
CHECK(soundBuffer.getSampleCount() == 87798);
|
||||||
|
CHECK(soundBuffer.getSampleRate() == 44100);
|
||||||
|
CHECK(soundBuffer.getChannelCount() == 1);
|
||||||
|
CHECK(soundBuffer.getDuration() == sf::microseconds(1990884));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("loadFromMemory()")
|
||||||
|
{
|
||||||
|
sf::SoundBuffer soundBuffer;
|
||||||
|
|
||||||
|
SECTION("Invalid memory")
|
||||||
|
{
|
||||||
|
CHECK(!soundBuffer.loadFromMemory(nullptr, 0));
|
||||||
|
constexpr std::array<std::byte, 5> memory{};
|
||||||
|
CHECK(!soundBuffer.loadFromMemory(memory.data(), memory.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Valid memory")
|
||||||
|
{
|
||||||
|
const auto memory = loadIntoMemory("Audio/ding.flac");
|
||||||
|
REQUIRE(soundBuffer.loadFromMemory(memory.data(), memory.size()));
|
||||||
|
CHECK(soundBuffer.getSamples() != nullptr);
|
||||||
|
CHECK(soundBuffer.getSampleCount() == 87798);
|
||||||
|
CHECK(soundBuffer.getSampleRate() == 44100);
|
||||||
|
CHECK(soundBuffer.getChannelCount() == 1);
|
||||||
|
CHECK(soundBuffer.getDuration() == sf::microseconds(1990884));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("loadFromStream()")
|
||||||
|
{
|
||||||
|
sf::FileInputStream stream;
|
||||||
|
sf::SoundBuffer soundBuffer;
|
||||||
|
|
||||||
|
SECTION("Invalid stream")
|
||||||
|
{
|
||||||
|
CHECK(!soundBuffer.loadFromStream(stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Valid stream")
|
||||||
|
{
|
||||||
|
REQUIRE(stream.open("Audio/ding.flac"));
|
||||||
|
REQUIRE(soundBuffer.loadFromStream(stream));
|
||||||
|
CHECK(soundBuffer.getSamples() != nullptr);
|
||||||
|
CHECK(soundBuffer.getSampleCount() == 87798);
|
||||||
|
CHECK(soundBuffer.getSampleRate() == 44100);
|
||||||
|
CHECK(soundBuffer.getChannelCount() == 1);
|
||||||
|
CHECK(soundBuffer.getDuration() == sf::microseconds(1990884));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("saveToFile()")
|
||||||
|
{
|
||||||
|
const auto filename = std::filesystem::temp_directory_path() / "ding.flac";
|
||||||
|
|
||||||
|
{
|
||||||
|
sf::SoundBuffer soundBuffer;
|
||||||
|
REQUIRE(soundBuffer.loadFromFile("Audio/ding.flac"));
|
||||||
|
REQUIRE(soundBuffer.saveToFile(filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
sf::SoundBuffer soundBuffer;
|
||||||
|
REQUIRE(soundBuffer.loadFromFile(filename));
|
||||||
|
CHECK(soundBuffer.getSamples() != nullptr);
|
||||||
|
CHECK(soundBuffer.getSampleCount() == 87798);
|
||||||
|
CHECK(soundBuffer.getSampleRate() == 44100);
|
||||||
|
CHECK(soundBuffer.getChannelCount() == 1);
|
||||||
|
CHECK(soundBuffer.getDuration() == sf::microseconds(1990884));
|
||||||
|
|
||||||
|
CHECK(std::filesystem::remove(filename));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,10 +1,134 @@
|
|||||||
#include <SFML/Audio/SoundSource.hpp>
|
#include <SFML/Audio/SoundSource.hpp>
|
||||||
|
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
#include <AudioUtil.hpp>
|
||||||
|
#include <SystemUtil.hpp>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
static_assert(!std::is_constructible_v<sf::SoundSource>);
|
namespace
|
||||||
static_assert(!std::is_copy_constructible_v<sf::SoundSource>);
|
{
|
||||||
static_assert(std::is_copy_assignable_v<sf::SoundSource>);
|
class SoundSource : public sf::SoundSource
|
||||||
static_assert(!std::is_move_constructible_v<sf::SoundSource>);
|
{
|
||||||
static_assert(std::is_move_assignable_v<sf::SoundSource>);
|
void play() override
|
||||||
static_assert(!std::is_nothrow_move_assignable_v<sf::SoundSource>);
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void pause() override
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() override
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void* getSound() const override
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
Status getStatus() const override
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("[Audio] sf::SoundSource", runAudioDeviceTests())
|
||||||
|
{
|
||||||
|
SECTION("Type traits")
|
||||||
|
{
|
||||||
|
STATIC_CHECK(!std::is_constructible_v<sf::SoundSource>);
|
||||||
|
STATIC_CHECK(!std::is_copy_constructible_v<sf::SoundSource>);
|
||||||
|
STATIC_CHECK(std::is_copy_assignable_v<sf::SoundSource>);
|
||||||
|
STATIC_CHECK(!std::is_move_constructible_v<sf::SoundSource>);
|
||||||
|
STATIC_CHECK(std::is_move_assignable_v<sf::SoundSource>);
|
||||||
|
STATIC_CHECK(!std::is_nothrow_move_assignable_v<sf::SoundSource>);
|
||||||
|
STATIC_CHECK(std::has_virtual_destructor_v<sf::SoundSource>);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Construction")
|
||||||
|
{
|
||||||
|
const SoundSource soundSource;
|
||||||
|
CHECK(soundSource.getPitch() == 0);
|
||||||
|
CHECK(soundSource.getVolume() == 0);
|
||||||
|
CHECK(soundSource.getPosition() == sf::Vector3f());
|
||||||
|
CHECK(!soundSource.isRelativeToListener());
|
||||||
|
CHECK(soundSource.getMinDistance() == 0);
|
||||||
|
CHECK(soundSource.getAttenuation() == 0);
|
||||||
|
CHECK(soundSource.getStatus() == sf::SoundSource::Status::Stopped);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Copy semantics")
|
||||||
|
{
|
||||||
|
const SoundSource soundSource;
|
||||||
|
|
||||||
|
SECTION("Construction")
|
||||||
|
{
|
||||||
|
const SoundSource soundSourceCopy(soundSource); // NOLINT(performance-unnecessary-copy-initialization)
|
||||||
|
CHECK(soundSourceCopy.getPitch() == 0);
|
||||||
|
CHECK(soundSourceCopy.getVolume() == 0);
|
||||||
|
CHECK(soundSourceCopy.getPosition() == sf::Vector3f());
|
||||||
|
CHECK(!soundSourceCopy.isRelativeToListener());
|
||||||
|
CHECK(soundSourceCopy.getMinDistance() == 0);
|
||||||
|
CHECK(soundSourceCopy.getAttenuation() == 0);
|
||||||
|
CHECK(soundSourceCopy.getStatus() == sf::SoundSource::Status::Stopped);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Assignment")
|
||||||
|
{
|
||||||
|
SoundSource soundSourceCopy;
|
||||||
|
soundSourceCopy = soundSource;
|
||||||
|
CHECK(soundSourceCopy.getPitch() == 0);
|
||||||
|
CHECK(soundSourceCopy.getVolume() == 0);
|
||||||
|
CHECK(soundSourceCopy.getPosition() == sf::Vector3f());
|
||||||
|
CHECK(!soundSourceCopy.isRelativeToListener());
|
||||||
|
CHECK(soundSourceCopy.getMinDistance() == 0);
|
||||||
|
CHECK(soundSourceCopy.getAttenuation() == 0);
|
||||||
|
CHECK(soundSourceCopy.getStatus() == sf::SoundSource::Status::Stopped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Set/get pitch")
|
||||||
|
{
|
||||||
|
SoundSource soundSource;
|
||||||
|
soundSource.setPitch(42);
|
||||||
|
CHECK(soundSource.getPitch() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Set/get volume")
|
||||||
|
{
|
||||||
|
SoundSource soundSource;
|
||||||
|
soundSource.setVolume(0.5f);
|
||||||
|
CHECK(soundSource.getVolume() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Set/get position")
|
||||||
|
{
|
||||||
|
SoundSource soundSource;
|
||||||
|
soundSource.setPosition({1, 2, 3});
|
||||||
|
CHECK(soundSource.getPosition() == sf::Vector3f());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Set/get relative to listener")
|
||||||
|
{
|
||||||
|
SoundSource soundSource;
|
||||||
|
soundSource.setRelativeToListener(true);
|
||||||
|
CHECK(!soundSource.isRelativeToListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Set/get min distance")
|
||||||
|
{
|
||||||
|
SoundSource soundSource;
|
||||||
|
soundSource.setMinDistance(12.34f);
|
||||||
|
CHECK(soundSource.getMinDistance() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Set/get attenuation")
|
||||||
|
{
|
||||||
|
SoundSource soundSource;
|
||||||
|
soundSource.setAttenuation(10);
|
||||||
|
CHECK(soundSource.getAttenuation() == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,9 +2,26 @@
|
|||||||
|
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
#include <AudioUtil.hpp>
|
||||||
|
#include <SystemUtil.hpp>
|
||||||
#include <type_traits>
|
#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")
|
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_copy_assignable_v<sf::SoundStream>);
|
||||||
STATIC_CHECK(!std::is_nothrow_move_constructible_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::is_nothrow_move_assignable_v<sf::SoundStream>);
|
||||||
|
STATIC_CHECK(std::has_virtual_destructor_v<sf::SoundStream>);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("Chunk")
|
SECTION("Chunk")
|
||||||
@ -21,4 +39,28 @@ TEST_CASE("[Audio] sf::SoundStream")
|
|||||||
CHECK(chunk.samples == nullptr);
|
CHECK(chunk.samples == nullptr);
|
||||||
CHECK(chunk.sampleCount == 0);
|
CHECK(chunk.sampleCount == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("Construction")
|
||||||
|
{
|
||||||
|
const SoundStream soundStream;
|
||||||
|
CHECK(soundStream.getChannelCount() == 0);
|
||||||
|
CHECK(soundStream.getSampleRate() == 0);
|
||||||
|
CHECK(soundStream.getStatus() == sf::SoundStream::Status::Stopped);
|
||||||
|
CHECK(soundStream.getPlayingOffset() == sf::Time::Zero);
|
||||||
|
CHECK(!soundStream.getLoop());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Set/get playing offset")
|
||||||
|
{
|
||||||
|
SoundStream soundStream;
|
||||||
|
soundStream.setPlayingOffset(sf::milliseconds(100));
|
||||||
|
CHECK(soundStream.getPlayingOffset() == sf::milliseconds(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Set/get loop")
|
||||||
|
{
|
||||||
|
SoundStream soundStream;
|
||||||
|
soundStream.setLoop(true);
|
||||||
|
CHECK(soundStream.getLoop());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ set(CATCH_CONFIG_FAST_COMPILE ON CACHE BOOL "")
|
|||||||
set(CATCH_CONFIG_NO_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT ON CACHE BOOL "")
|
set(CATCH_CONFIG_NO_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT ON CACHE BOOL "")
|
||||||
FetchContent_Declare(Catch2
|
FetchContent_Declare(Catch2
|
||||||
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
||||||
GIT_TAG v3.5.4
|
GIT_TAG v3.6.0
|
||||||
GIT_SHALLOW ON)
|
GIT_SHALLOW ON)
|
||||||
FetchContent_MakeAvailable(Catch2)
|
FetchContent_MakeAvailable(Catch2)
|
||||||
include(Catch)
|
include(Catch)
|
||||||
@ -28,6 +28,8 @@ add_library(sfml-test-main STATIC
|
|||||||
TestUtilities/WindowUtil.cpp
|
TestUtilities/WindowUtil.cpp
|
||||||
TestUtilities/GraphicsUtil.hpp
|
TestUtilities/GraphicsUtil.hpp
|
||||||
TestUtilities/GraphicsUtil.cpp
|
TestUtilities/GraphicsUtil.cpp
|
||||||
|
TestUtilities/AudioUtil.hpp
|
||||||
|
TestUtilities/AudioUtil.cpp
|
||||||
)
|
)
|
||||||
target_include_directories(sfml-test-main PUBLIC TestUtilities)
|
target_include_directories(sfml-test-main PUBLIC TestUtilities)
|
||||||
target_link_libraries(sfml-test-main PUBLIC SFML::System Catch2::Catch2WithMain)
|
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)
|
target_compile_definitions(sfml-test-main PRIVATE SFML_RUN_DISPLAY_TESTS)
|
||||||
endif()
|
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
|
set(SYSTEM_SRC
|
||||||
System/Angle.test.cpp
|
System/Angle.test.cpp
|
||||||
System/Clock.test.cpp
|
System/Clock.test.cpp
|
||||||
@ -145,6 +152,41 @@ set(AUDIO_SRC
|
|||||||
)
|
)
|
||||||
sfml_add_test(test-sfml-audio "${AUDIO_SRC}" SFML::Audio)
|
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)
|
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
|
# Try to find and use OpenCppCoverage for coverage reporting when building with MSVC
|
||||||
find_program(OpenCppCoverage_BINARY "OpenCppCoverage.exe")
|
find_program(OpenCppCoverage_BINARY "OpenCppCoverage.exe")
|
||||||
|
@ -34,11 +34,11 @@ TEST_CASE("[Graphics] sf::Transformable")
|
|||||||
CHECK(transformable.getPosition() == sf::Vector2f(3, 4));
|
CHECK(transformable.getPosition() == sf::Vector2f(3, 4));
|
||||||
|
|
||||||
transformable.setRotation(sf::degrees(3.14f));
|
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));
|
transformable.setRotation(sf::degrees(540));
|
||||||
CHECK(transformable.getRotation() == sf::degrees(180));
|
CHECK(transformable.getRotation() == Approx(sf::degrees(180)));
|
||||||
transformable.setRotation(sf::degrees(-72));
|
transformable.setRotation(sf::degrees(-72));
|
||||||
CHECK(transformable.getRotation() == sf::degrees(288));
|
CHECK(transformable.getRotation() == Approx(sf::degrees(288)));
|
||||||
|
|
||||||
transformable.setScale({5, 6});
|
transformable.setScale({5, 6});
|
||||||
CHECK(transformable.getScale() == sf::Vector2f(5, 6));
|
CHECK(transformable.getScale() == sf::Vector2f(5, 6));
|
||||||
@ -102,15 +102,15 @@ TEST_CASE("[Graphics] sf::Transformable")
|
|||||||
sf::Transformable transformable;
|
sf::Transformable transformable;
|
||||||
CHECK(transformable.getRotation() == sf::Angle::Zero);
|
CHECK(transformable.getRotation() == sf::Angle::Zero);
|
||||||
transformable.rotate(sf::degrees(15));
|
transformable.rotate(sf::degrees(15));
|
||||||
CHECK(transformable.getRotation() == sf::degrees(15));
|
CHECK(transformable.getRotation() == Approx(sf::degrees(15)));
|
||||||
transformable.rotate(sf::degrees(360));
|
transformable.rotate(sf::degrees(360));
|
||||||
CHECK(transformable.getRotation() == sf::degrees(15));
|
CHECK(transformable.getRotation() == Approx(sf::degrees(15)));
|
||||||
transformable.rotate(sf::degrees(-25));
|
transformable.rotate(sf::degrees(-25));
|
||||||
CHECK(transformable.getRotation() == sf::degrees(350));
|
CHECK(transformable.getRotation() == Approx(sf::degrees(350)));
|
||||||
transformable.rotate(sf::degrees(-720));
|
transformable.rotate(sf::degrees(-720));
|
||||||
CHECK(transformable.getRotation() == sf::degrees(350));
|
CHECK(transformable.getRotation() == Approx(sf::degrees(350)));
|
||||||
transformable.rotate(sf::degrees(-370));
|
transformable.rotate(sf::degrees(-370));
|
||||||
CHECK(transformable.getRotation() == sf::degrees(340));
|
CHECK(transformable.getRotation() == Approx(sf::degrees(340)));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("scale()")
|
SECTION("scale()")
|
||||||
|
@ -76,13 +76,13 @@ TEST_CASE("[Graphics] sf::View")
|
|||||||
{
|
{
|
||||||
sf::View view;
|
sf::View view;
|
||||||
view.setRotation(sf::degrees(-345));
|
view.setRotation(sf::degrees(-345));
|
||||||
CHECK(view.getRotation() == sf::degrees(15));
|
CHECK(view.getRotation() == Approx(sf::degrees(15)));
|
||||||
CHECK(view.getTransform() ==
|
CHECK(view.getTransform() ==
|
||||||
Approx(sf::Transform(0.00193185f, 0.000517638f, -1.22474f, 0.000517638f, -0.00193185f, 0.707107f, 0, 0, 1)));
|
Approx(sf::Transform(0.00193185f, 0.000517638f, -1.22474f, 0.000517638f, -0.00193185f, 0.707107f, 0, 0, 1)));
|
||||||
CHECK(view.getInverseTransform() ==
|
CHECK(view.getInverseTransform() ==
|
||||||
Approx(sf::Transform(482.963f, 129.41f, 500, 129.41f, -482.963f, 500, 0, 0, 1)));
|
Approx(sf::Transform(482.963f, 129.41f, 500, 129.41f, -482.963f, 500, 0, 0, 1)));
|
||||||
view.setRotation(sf::degrees(400));
|
view.setRotation(sf::degrees(400));
|
||||||
CHECK(view.getRotation() == sf::degrees(40));
|
CHECK(view.getRotation() == Approx(sf::degrees(40)));
|
||||||
CHECK(view.getTransform() ==
|
CHECK(view.getTransform() ==
|
||||||
Approx(sf::Transform(0.00153209f, 0.00128558f, -1.40883f, 0.00128558f, -0.00153209f, 0.123257f, 0, 0, 1)));
|
Approx(sf::Transform(0.00153209f, 0.00128558f, -1.40883f, 0.00128558f, -0.00153209f, 0.123257f, 0, 0, 1)));
|
||||||
CHECK(view.getInverseTransform() ==
|
CHECK(view.getInverseTransform() ==
|
||||||
@ -140,7 +140,7 @@ TEST_CASE("[Graphics] sf::View")
|
|||||||
sf::View view;
|
sf::View view;
|
||||||
view.setRotation(sf::degrees(45));
|
view.setRotation(sf::degrees(45));
|
||||||
view.rotate(sf::degrees(-15));
|
view.rotate(sf::degrees(-15));
|
||||||
CHECK(view.getRotation() == sf::degrees(30));
|
CHECK(view.getRotation() == Approx(sf::degrees(30)));
|
||||||
CHECK(view.getTransform() ==
|
CHECK(view.getTransform() ==
|
||||||
Approx(sf::Transform(0.00173205f, 0.001f, -1.36603f, 0.001f, -0.00173205f, 0.366025f, 0, 0, 1)));
|
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)));
|
CHECK(view.getInverseTransform() == Approx(sf::Transform(433.013f, 250, 500, 250, -433.013f, 500, 0, 0, 1)));
|
||||||
|
@ -25,33 +25,33 @@ TEST_CASE("[System] sf::Angle")
|
|||||||
SECTION("wrapSigned()")
|
SECTION("wrapSigned()")
|
||||||
{
|
{
|
||||||
STATIC_CHECK(sf::Angle::Zero.wrapSigned() == sf::Angle::Zero);
|
STATIC_CHECK(sf::Angle::Zero.wrapSigned() == sf::Angle::Zero);
|
||||||
STATIC_CHECK(sf::degrees(0).wrapSigned() == sf::degrees(0));
|
CHECK(sf::degrees(0).wrapSigned() == Approx(sf::degrees(0)));
|
||||||
STATIC_CHECK(sf::degrees(1).wrapSigned() == sf::degrees(1));
|
CHECK(sf::degrees(1).wrapSigned() == Approx(sf::degrees(1)));
|
||||||
STATIC_CHECK(sf::degrees(-1).wrapSigned() == sf::degrees(-1));
|
CHECK(sf::degrees(-1).wrapSigned() == Approx(sf::degrees(-1)));
|
||||||
STATIC_CHECK(sf::degrees(90).wrapSigned() == sf::degrees(90));
|
CHECK(sf::degrees(90).wrapSigned() == Approx(sf::degrees(90)));
|
||||||
STATIC_CHECK(sf::degrees(-90).wrapSigned() == sf::degrees(-90));
|
CHECK(sf::degrees(-90).wrapSigned() == Approx(sf::degrees(-90)));
|
||||||
STATIC_CHECK(sf::degrees(180).wrapSigned() == sf::degrees(-180));
|
CHECK(sf::degrees(180).wrapSigned() == Approx(sf::degrees(-180)));
|
||||||
STATIC_CHECK(sf::degrees(-180).wrapSigned() == sf::degrees(-180));
|
CHECK(sf::degrees(-180).wrapSigned() == Approx(sf::degrees(-180)));
|
||||||
STATIC_CHECK(sf::degrees(360).wrapSigned() == sf::degrees(0));
|
CHECK(sf::degrees(360).wrapSigned() == Approx(sf::degrees(0)));
|
||||||
STATIC_CHECK(sf::degrees(-360).wrapSigned() == sf::degrees(0));
|
CHECK(sf::degrees(-360).wrapSigned() == Approx(sf::degrees(0)));
|
||||||
STATIC_CHECK(sf::degrees(720).wrapSigned() == sf::degrees(0));
|
CHECK(sf::degrees(720).wrapSigned() == Approx(sf::degrees(0)));
|
||||||
STATIC_CHECK(sf::degrees(-720).wrapSigned() == sf::degrees(0));
|
CHECK(sf::degrees(-720).wrapSigned() == Approx(sf::degrees(0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("wrapUnsigned()")
|
SECTION("wrapUnsigned()")
|
||||||
{
|
{
|
||||||
STATIC_CHECK(sf::Angle::Zero.wrapUnsigned() == sf::Angle::Zero);
|
STATIC_CHECK(sf::Angle::Zero.wrapUnsigned() == sf::Angle::Zero);
|
||||||
STATIC_CHECK(sf::degrees(0).wrapUnsigned() == sf::degrees(0));
|
CHECK(sf::degrees(0).wrapUnsigned() == Approx(sf::degrees(0)));
|
||||||
STATIC_CHECK(sf::degrees(1).wrapUnsigned() == sf::degrees(1));
|
CHECK(sf::degrees(1).wrapUnsigned() == Approx(sf::degrees(1)));
|
||||||
STATIC_CHECK(sf::degrees(-1).wrapUnsigned() == sf::degrees(359));
|
CHECK(sf::degrees(-1).wrapUnsigned() == Approx(sf::degrees(359)));
|
||||||
STATIC_CHECK(sf::degrees(90).wrapUnsigned() == sf::degrees(90));
|
CHECK(sf::degrees(90).wrapUnsigned() == Approx(sf::degrees(90)));
|
||||||
STATIC_CHECK(sf::degrees(-90).wrapUnsigned() == sf::degrees(270));
|
CHECK(sf::degrees(-90).wrapUnsigned() == Approx(sf::degrees(270)));
|
||||||
STATIC_CHECK(sf::degrees(180).wrapUnsigned() == sf::degrees(180));
|
CHECK(sf::degrees(180).wrapUnsigned() == Approx(sf::degrees(180)));
|
||||||
STATIC_CHECK(sf::degrees(-180).wrapUnsigned() == sf::degrees(180));
|
CHECK(sf::degrees(-180).wrapUnsigned() == Approx(sf::degrees(180)));
|
||||||
STATIC_CHECK(sf::degrees(360).wrapUnsigned() == sf::degrees(0));
|
CHECK(sf::degrees(360).wrapUnsigned() == Approx(sf::degrees(0)));
|
||||||
STATIC_CHECK(sf::degrees(-360).wrapUnsigned() == sf::degrees(0));
|
CHECK(sf::degrees(-360).wrapUnsigned() == Approx(sf::degrees(0)));
|
||||||
STATIC_CHECK(sf::degrees(720).wrapUnsigned() == sf::degrees(0));
|
CHECK(sf::degrees(720).wrapUnsigned() == Approx(sf::degrees(0)));
|
||||||
STATIC_CHECK(sf::degrees(-720).wrapUnsigned() == sf::degrees(0));
|
CHECK(sf::degrees(-720).wrapUnsigned() == Approx(sf::degrees(0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("degrees()")
|
SECTION("degrees()")
|
||||||
@ -185,7 +185,7 @@ TEST_CASE("[System] sf::Angle")
|
|||||||
{
|
{
|
||||||
sf::Angle angle = sf::degrees(-15);
|
sf::Angle angle = sf::degrees(-15);
|
||||||
angle += sf::degrees(15);
|
angle += sf::degrees(15);
|
||||||
CHECK(angle == sf::degrees(0));
|
CHECK(angle == Approx(sf::degrees(0)));
|
||||||
angle += sf::radians(10);
|
angle += sf::radians(10);
|
||||||
CHECK(angle == sf::radians(10));
|
CHECK(angle == sf::radians(10));
|
||||||
}
|
}
|
||||||
@ -202,7 +202,7 @@ TEST_CASE("[System] sf::Angle")
|
|||||||
{
|
{
|
||||||
sf::Angle angle = sf::degrees(15);
|
sf::Angle angle = sf::degrees(15);
|
||||||
angle -= sf::degrees(15);
|
angle -= sf::degrees(15);
|
||||||
CHECK(angle == sf::degrees(0));
|
CHECK(angle == Approx(sf::degrees(0)));
|
||||||
angle -= sf::radians(10);
|
angle -= sf::radians(10);
|
||||||
CHECK(angle == sf::radians(-10));
|
CHECK(angle == sf::radians(-10));
|
||||||
}
|
}
|
||||||
@ -210,19 +210,19 @@ TEST_CASE("[System] sf::Angle")
|
|||||||
SECTION("operator*")
|
SECTION("operator*")
|
||||||
{
|
{
|
||||||
STATIC_CHECK(sf::radians(0) * 10 == sf::Angle::Zero);
|
STATIC_CHECK(sf::radians(0) * 10 == sf::Angle::Zero);
|
||||||
STATIC_CHECK(sf::degrees(10) * 2.5f == sf::degrees(25));
|
CHECK(sf::degrees(10) * 2.5f == Approx(sf::degrees(25)));
|
||||||
STATIC_CHECK(sf::degrees(100) * 10.0f == sf::degrees(1000));
|
CHECK(sf::degrees(100) * 10.0f == Approx(sf::degrees(1000)));
|
||||||
|
|
||||||
STATIC_CHECK(10 * sf::radians(0) == sf::Angle::Zero);
|
STATIC_CHECK(10 * sf::radians(0) == sf::Angle::Zero);
|
||||||
STATIC_CHECK(2.5f * sf::degrees(10) == sf::degrees(25));
|
CHECK(2.5f * sf::degrees(10) == Approx(sf::degrees(25)));
|
||||||
STATIC_CHECK(10.0f * sf::degrees(100) == sf::degrees(1000));
|
CHECK(10.0f * sf::degrees(100) == Approx(sf::degrees(1000)));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("operator*=")
|
SECTION("operator*=")
|
||||||
{
|
{
|
||||||
sf::Angle angle = sf::degrees(1);
|
sf::Angle angle = sf::degrees(1);
|
||||||
angle *= 10;
|
angle *= 10;
|
||||||
CHECK(angle == sf::degrees(10));
|
CHECK(angle == Approx(sf::degrees(10)));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("operator/")
|
SECTION("operator/")
|
||||||
@ -240,24 +240,24 @@ TEST_CASE("[System] sf::Angle")
|
|||||||
{
|
{
|
||||||
sf::Angle angle = sf::degrees(60);
|
sf::Angle angle = sf::degrees(60);
|
||||||
angle /= 5;
|
angle /= 5;
|
||||||
CHECK(angle == sf::degrees(12));
|
CHECK(angle == Approx(sf::degrees(12)));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("operator%")
|
SECTION("operator%")
|
||||||
{
|
{
|
||||||
STATIC_CHECK(sf::Angle::Zero % sf::radians(0.5f) == sf::Angle::Zero);
|
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::radians(10) % sf::radians(1) == sf::radians(0));
|
||||||
STATIC_CHECK(sf::degrees(90) % sf::degrees(30) == sf::degrees(0));
|
CHECK(sf::degrees(90) % sf::degrees(30) == Approx(sf::degrees(0)));
|
||||||
STATIC_CHECK(sf::degrees(90) % sf::degrees(40) == sf::degrees(10));
|
CHECK(sf::degrees(90) % sf::degrees(40) == Approx(sf::degrees(10)));
|
||||||
STATIC_CHECK(sf::degrees(-90) % sf::degrees(30) == sf::degrees(0));
|
CHECK(sf::degrees(-90) % sf::degrees(30) == Approx(sf::degrees(0)));
|
||||||
STATIC_CHECK(sf::degrees(-90) % sf::degrees(40) == sf::degrees(30));
|
CHECK(sf::degrees(-90) % sf::degrees(40) == Approx(sf::degrees(30)));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("operator%=")
|
SECTION("operator%=")
|
||||||
{
|
{
|
||||||
sf::Angle angle = sf::degrees(59);
|
sf::Angle angle = sf::degrees(59);
|
||||||
angle %= sf::degrees(10);
|
angle %= sf::degrees(10);
|
||||||
CHECK(angle == sf::degrees(9));
|
CHECK(angle == Approx(sf::degrees(9)));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("operator _deg")
|
SECTION("operator _deg")
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
@ -32,9 +33,9 @@ TEST_CASE("[System] sf::MemoryInputStream")
|
|||||||
sf::MemoryInputStream mis;
|
sf::MemoryInputStream mis;
|
||||||
mis.open(memoryContents.data(), sizeof(char) * memoryContents.size());
|
mis.open(memoryContents.data(), sizeof(char) * memoryContents.size());
|
||||||
|
|
||||||
char buffer[32];
|
std::array<char, 32> buffer{};
|
||||||
CHECK(mis.read(buffer, 5) == 5);
|
CHECK(mis.read(buffer.data(), 5) == 5);
|
||||||
CHECK(std::string_view(buffer, 5) == std::string_view(memoryContents.data(), 5));
|
CHECK(std::string_view(buffer.data(), 5) == std::string_view(memoryContents.data(), 5));
|
||||||
CHECK(mis.seek(10) == 10);
|
CHECK(mis.seek(10) == 10);
|
||||||
CHECK(mis.tell() == 10);
|
CHECK(mis.tell() == 10);
|
||||||
CHECK(mis.getSize() == 11);
|
CHECK(mis.getSize() == 11);
|
||||||
|
12
test/TestUtilities/AudioUtil.cpp
Normal file
12
test/TestUtilities/AudioUtil.cpp
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#include <AudioUtil.hpp>
|
||||||
|
|
||||||
|
std::string runAudioDeviceTests()
|
||||||
|
{
|
||||||
|
#ifdef SFML_RUN_AUDIO_DEVICE_TESTS
|
||||||
|
return "";
|
||||||
|
#else
|
||||||
|
// https://github.com/catchorg/Catch2/blob/devel/docs/test-cases-and-sections.md#special-tags
|
||||||
|
// This tag tells Catch2 to not run a given TEST_CASE
|
||||||
|
return "[.audio_device]";
|
||||||
|
#endif
|
||||||
|
}
|
5
test/TestUtilities/AudioUtil.hpp
Normal file
5
test/TestUtilities/AudioUtil.hpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
[[nodiscard]] std::string runAudioDeviceTests();
|
@ -77,7 +77,7 @@ bool operator==(const sf::Vector3f& lhs, const Approx<sf::Vector3f>& rhs)
|
|||||||
|
|
||||||
bool operator==(const sf::Angle& lhs, const Approx<sf::Angle>& rhs)
|
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)
|
std::vector<std::byte> loadIntoMemory(const std::filesystem::path& path)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
prefix=@CMAKE_INSTALL_PREFIX@
|
prefix=@CMAKE_INSTALL_PREFIX@
|
||||||
exec_prefix=${prefix}
|
exec_prefix=${prefix}
|
||||||
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
|
libdir=${exec_prefix}/@SFML_RELATIVE_INSTALL_LIBDIR@
|
||||||
includedir=${prefix}/include
|
includedir=${prefix}/include
|
||||||
|
|
||||||
Name: SFML-all
|
Name: SFML-all
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
prefix=@CMAKE_INSTALL_PREFIX@
|
prefix=@CMAKE_INSTALL_PREFIX@
|
||||||
exec_prefix=${prefix}
|
exec_prefix=${prefix}
|
||||||
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
|
libdir=${exec_prefix}/@SFML_RELATIVE_INSTALL_LIBDIR@
|
||||||
includedir=${prefix}/include
|
includedir=${prefix}/include
|
||||||
|
|
||||||
Name: SFML-audio
|
Name: SFML-audio
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
prefix=@CMAKE_INSTALL_PREFIX@
|
prefix=@CMAKE_INSTALL_PREFIX@
|
||||||
exec_prefix=${prefix}
|
exec_prefix=${prefix}
|
||||||
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
|
libdir=${exec_prefix}/@SFML_RELATIVE_INSTALL_LIBDIR@
|
||||||
includedir=${prefix}/include
|
includedir=${prefix}/include
|
||||||
|
|
||||||
Name: SFML-graphics
|
Name: SFML-graphics
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
prefix=@CMAKE_INSTALL_PREFIX@
|
prefix=@CMAKE_INSTALL_PREFIX@
|
||||||
exec_prefix=${prefix}
|
exec_prefix=${prefix}
|
||||||
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
|
libdir=${exec_prefix}/@SFML_RELATIVE_INSTALL_LIBDIR@
|
||||||
includedir=${prefix}/include
|
includedir=${prefix}/include
|
||||||
|
|
||||||
Name: SFML-network
|
Name: SFML-network
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
prefix=@CMAKE_INSTALL_PREFIX@
|
prefix=@CMAKE_INSTALL_PREFIX@
|
||||||
exec_prefix=${prefix}
|
exec_prefix=${prefix}
|
||||||
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
|
libdir=${exec_prefix}/@SFML_RELATIVE_INSTALL_LIBDIR@
|
||||||
includedir=${prefix}/include
|
includedir=${prefix}/include
|
||||||
|
|
||||||
Name: SFML-system
|
Name: SFML-system
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
prefix=@CMAKE_INSTALL_PREFIX@
|
prefix=@CMAKE_INSTALL_PREFIX@
|
||||||
exec_prefix=${prefix}
|
exec_prefix=${prefix}
|
||||||
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
|
libdir=${exec_prefix}/@SFML_RELATIVE_INSTALL_LIBDIR@
|
||||||
includedir=${prefix}/include
|
includedir=${prefix}/include
|
||||||
|
|
||||||
Name: SFML-window
|
Name: SFML-window
|
||||||
|
Loading…
Reference in New Issue
Block a user